Commit 9c9656da authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Expand annotation-author helper functions

parent 132daf05
...@@ -3,14 +3,13 @@ import { useMemo } from 'preact/hooks'; ...@@ -3,14 +3,13 @@ import { useMemo } from 'preact/hooks';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
import { useSidebarStore } from '../../store'; import { useSidebarStore } from '../../store';
import { isThirdPartyUser, username } from '../../helpers/account-id';
import { import {
domainAndTitle, domainAndTitle,
isHighlight, isHighlight,
isReply, isReply,
hasBeenEdited, hasBeenEdited,
} from '../../helpers/annotation-metadata'; } from '../../helpers/annotation-metadata';
import { annotationDisplayName } from '../../helpers/annotation-user'; import { annotationAuthorInfo } from '../../helpers/annotation-user';
import { isPrivate } from '../../helpers/permissions'; import { isPrivate } from '../../helpers/permissions';
import AnnotationDocumentInfo from './AnnotationDocumentInfo'; import AnnotationDocumentInfo from './AnnotationDocumentInfo';
...@@ -57,27 +56,11 @@ function AnnotationHeader({ ...@@ -57,27 +56,11 @@ function AnnotationHeader({
settings, settings,
}) { }) {
const store = useSidebarStore(); const store = useSidebarStore();
const defaultAuthority = store.defaultAuthority();
const displayNamesEnabled = store.isFeatureEnabled('client_display_names');
const isThirdParty = isThirdPartyUser(annotation.user, defaultAuthority);
const authorDisplayName = annotationDisplayName(
annotation,
isThirdParty,
displayNamesEnabled
);
const authorLink = (() => { const { authorDisplayName, authorLink } = useMemo(
if (!isThirdParty) { () => annotationAuthorInfo(annotation, store, settings),
return store.getLink('user', { user: annotation.user }); [annotation, store, settings]
} else { );
return (
(settings.usernameUrl &&
`${settings.usernameUrl}${username(annotation.user)}`) ??
undefined
);
}
})();
const isCollapsedReply = isReply(annotation) && threadIsCollapsed; const isCollapsedReply = isReply(annotation) && threadIsCollapsed;
......
...@@ -8,8 +8,7 @@ import { mockImportedComponents } from '../../../../test-util/mock-imported-comp ...@@ -8,8 +8,7 @@ import { mockImportedComponents } from '../../../../test-util/mock-imported-comp
import AnnotationHeader, { $imports } from '../AnnotationHeader'; import AnnotationHeader, { $imports } from '../AnnotationHeader';
describe('AnnotationHeader', () => { describe('AnnotationHeader', () => {
let fakeAccountId; let fakeAnnotationAuthorInfo;
let fakeAnnotationDisplayName;
let fakeDomainAndTitle; let fakeDomainAndTitle;
let fakeGroup; let fakeGroup;
let fakeIsHighlight; let fakeIsHighlight;
...@@ -46,20 +45,15 @@ describe('AnnotationHeader', () => { ...@@ -46,20 +45,15 @@ describe('AnnotationHeader', () => {
fakeHasBeenEdited = sinon.stub().returns(false); fakeHasBeenEdited = sinon.stub().returns(false);
fakeIsPrivate = sinon.stub(); fakeIsPrivate = sinon.stub();
fakeAccountId = { fakeAnnotationAuthorInfo = sinon.stub().returns({
isThirdPartyUser: sinon.stub().returns(false), authorDisplayName: 'Robbie Burns',
username: sinon.stub().returnsArg(0), authorLink: 'http://www.example.com',
}; });
fakeAnnotationDisplayName = sinon.stub().returns('Robbie Burns');
fakeSettings = { usernameUrl: 'http://foo.bar/' }; fakeSettings = { usernameUrl: 'http://foo.bar/' };
fakeStore = { fakeStore = {
defaultAuthority: sinon.stub().returns('foo.com'),
getGroup: sinon.stub().returns(fakeGroup), getGroup: sinon.stub().returns(fakeGroup),
getLink: sinon.stub().returns('http://example.com'),
isFeatureEnabled: sinon.stub().returns(false),
route: sinon.stub().returns('sidebar'), route: sinon.stub().returns('sidebar'),
setExpanded: sinon.stub(), setExpanded: sinon.stub(),
}; };
...@@ -67,7 +61,6 @@ describe('AnnotationHeader', () => { ...@@ -67,7 +61,6 @@ describe('AnnotationHeader', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../../store': { useSidebarStore: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
'../../helpers/account-id': fakeAccountId,
'../../helpers/annotation-metadata': { '../../helpers/annotation-metadata': {
domainAndTitle: fakeDomainAndTitle, domainAndTitle: fakeDomainAndTitle,
isHighlight: fakeIsHighlight, isHighlight: fakeIsHighlight,
...@@ -75,7 +68,7 @@ describe('AnnotationHeader', () => { ...@@ -75,7 +68,7 @@ describe('AnnotationHeader', () => {
hasBeenEdited: fakeHasBeenEdited, hasBeenEdited: fakeHasBeenEdited,
}, },
'../../helpers/annotation-user': { '../../helpers/annotation-user': {
annotationDisplayName: fakeAnnotationDisplayName, annotationAuthorInfo: fakeAnnotationAuthorInfo,
}, },
'../../helpers/permissions': { '../../helpers/permissions': {
isPrivate: fakeIsPrivate, isPrivate: fakeIsPrivate,
...@@ -114,33 +107,22 @@ describe('AnnotationHeader', () => { ...@@ -114,33 +107,22 @@ describe('AnnotationHeader', () => {
}); });
describe('annotation author (user) information', () => { describe('annotation author (user) information', () => {
it('should link to author activity if first-party', () => { it('should link to author activity if link available', () => {
fakeAccountId.isThirdPartyUser.returns(false);
const wrapper = createAnnotationHeader(); const wrapper = createAnnotationHeader();
assert.equal( assert.equal(
wrapper.find('AnnotationUser').props().authorLink, wrapper.find('AnnotationUser').props().authorLink,
'http://example.com' 'http://www.example.com'
);
});
it('should link to author activity if third-party and has settings URL', () => {
fakeAccountId.isThirdPartyUser.returns(true);
const fakeAnnotation = fixtures.defaultAnnotation();
const wrapper = createAnnotationHeader({ annotation: fakeAnnotation });
assert.equal(
wrapper.find('AnnotationUser').props().authorLink,
`http://foo.bar/${fakeAnnotation.user}`
); );
}); });
it('should not link to author if third-party and no settings URL', () => { it('should not link to author if none provided', () => {
fakeAccountId.isThirdPartyUser.returns(true); fakeAnnotationAuthorInfo.returns({
authorDisplayName: 'Robbie Burns',
authorLink: undefined,
});
const wrapper = createAnnotationHeader({ settings: {} }); const wrapper = createAnnotationHeader();
assert.isUndefined(wrapper.find('AnnotationUser').props().authorLink); assert.isUndefined(wrapper.find('AnnotationUser').props().authorLink);
}); });
......
...@@ -51,9 +51,7 @@ describe('sidebar/components/hooks/use-user-filter-options', () => { ...@@ -51,9 +51,7 @@ describe('sidebar/components/hooks/use-user-filter-options', () => {
fakeStore = { fakeStore = {
allAnnotations: sinon.stub().returns([]), allAnnotations: sinon.stub().returns([]),
defaultAuthority: sinon.stub().returns('foo.com'),
getFocusFilters: sinon.stub().returns({}), getFocusFilters: sinon.stub().returns({}),
isFeatureEnabled: sinon.stub().returns(false),
profile: sinon.stub().returns({}), profile: sinon.stub().returns({}),
}; };
......
import { useMemo } from 'preact/hooks'; import { useMemo } from 'preact/hooks';
import { useSidebarStore } from '../../store'; import { useSidebarStore } from '../../store';
import { isThirdPartyUser, username } from '../../helpers/account-id'; import { username } from '../../helpers/account-id';
import { annotationDisplayName } from '../../helpers/annotation-user'; import { annotationDisplayName } from '../../helpers/annotation-user';
/** @typedef {import('../../store/modules/filters').FilterOption} FilterOption */ /** @typedef {import('../../store/modules/filters').FilterOption} FilterOption */
...@@ -15,8 +15,6 @@ import { annotationDisplayName } from '../../helpers/annotation-user'; ...@@ -15,8 +15,6 @@ import { annotationDisplayName } from '../../helpers/annotation-user';
export function useUserFilterOptions() { export function useUserFilterOptions() {
const store = useSidebarStore(); const store = useSidebarStore();
const annotations = store.allAnnotations(); const annotations = store.allAnnotations();
const defaultAuthority = store.defaultAuthority();
const displayNamesEnabled = store.isFeatureEnabled('client_display_names');
const focusFilters = store.getFocusFilters(); const focusFilters = store.getFocusFilters();
const currentUsername = username(store.profile().userid); const currentUsername = username(store.profile().userid);
...@@ -26,11 +24,7 @@ export function useUserFilterOptions() { ...@@ -26,11 +24,7 @@ export function useUserFilterOptions() {
const users = {}; const users = {};
annotations.forEach(annotation => { annotations.forEach(annotation => {
const username_ = username(annotation.user); const username_ = username(annotation.user);
users[username_] = annotationDisplayName( users[username_] = annotationDisplayName(annotation, store);
annotation,
isThirdPartyUser(annotation.user, defaultAuthority),
displayNamesEnabled
);
}); });
// If user-focus is configured (even if not applied) add a filter // If user-focus is configured (even if not applied) add a filter
...@@ -63,11 +57,5 @@ export function useUserFilterOptions() { ...@@ -63,11 +57,5 @@ export function useUserFilterOptions() {
}); });
return userOptions; return userOptions;
}, [ }, [annotations, currentUsername, focusFilters.user, store]);
annotations,
currentUsername,
defaultAuthority,
displayNamesEnabled,
focusFilters.user,
]);
} }
/** /**
* @typedef {import("../../types/api").Annotation} Annotation * @typedef {import("../../types/api").Annotation} Annotation
* @typedef {import('../../types/config').SidebarSettings} SidebarSettings
* @typedef {import('../store').SidebarStore} SidebarStore
*/ */
import { username } from './account-id'; import { isThirdPartyUser, username } from './account-id';
/** /**
* What string should we use to represent the author (user) of a given * What string should we use to represent the author (user) of a given
* annotation: a display name or a username? * annotation: a display name or a username?
...@@ -17,18 +18,56 @@ import { username } from './account-id'; ...@@ -17,18 +18,56 @@ import { username } from './account-id';
* username or the display name. * username or the display name.
* *
* @param {Pick<Annotation, 'user'|'user_info'>} annotation * @param {Pick<Annotation, 'user'|'user_info'>} annotation
* @param {boolean} isThirdPartyUser - Is the annotation's user third-party? * @param {SidebarStore} store
* @param {boolean} isFeatureFlagEnabled - Is the `client_display_names` *
* feature flag enabled
* @return {string} * @return {string}
*/ */
export function annotationDisplayName( export function annotationDisplayName(annotation, store) {
annotation, const defaultAuthority = store.defaultAuthority();
isThirdPartyUser, const isThirdParty = isThirdPartyUser(annotation.user, defaultAuthority);
isFeatureFlagEnabled
) { const displayNamesEnabled = store.isFeatureEnabled('client_display_names');
const useDisplayName = isFeatureFlagEnabled || isThirdPartyUser;
const useDisplayName = displayNamesEnabled || isThirdParty;
return useDisplayName && annotation.user_info?.display_name return useDisplayName && annotation.user_info?.display_name
? annotation.user_info.display_name ? annotation.user_info.display_name
: username(annotation.user); : username(annotation.user);
} }
/**
* Return a URL to the annotation author's user page, when available. Author
* links for third-party users are only available if a `usernameUrl` is
* provided in `settings`.
*
* @param {Pick<Annotation, 'user'>} annotation
* @param {SidebarStore} store
* @param {SidebarSettings} settings
*/
export function annotationAuthorLink(annotation, store, settings) {
const defaultAuthority = store.defaultAuthority();
const isThirdParty = isThirdPartyUser(annotation.user, defaultAuthority);
if (!isThirdParty) {
return store.getLink('user', { user: annotation.user });
}
return (
(settings.usernameUrl &&
`${settings.usernameUrl}${username(annotation.user)}`) ??
undefined
);
}
/**
* Retrieve both author display name and link.
*
* @param {Pick<Annotation, 'user'|'user_info'>} annotation
* @param {SidebarStore} store
* @param {SidebarSettings} settings
*/
export function annotationAuthorInfo(annotation, store, settings) {
return {
authorDisplayName: annotationDisplayName(annotation, store),
authorLink: annotationAuthorLink(annotation, store, settings),
};
}
import { annotationDisplayName } from '../annotation-user'; import {
annotationDisplayName,
annotationAuthorLink,
annotationAuthorInfo,
$imports,
} from '../annotation-user';
describe('sidebar/helpers/annotation-user', () => { describe('sidebar/helpers/annotation-user', () => {
let fakeAccountId;
let fakeSettings;
let fakeStore;
beforeEach(() => {
fakeSettings = { usernameUrl: 'http://foo.bar/' };
fakeStore = {
defaultAuthority: sinon.stub().returns('foo.com'),
isFeatureEnabled: sinon.stub().returns(false),
getLink: sinon
.stub()
.withArgs('user')
.returns('http://www.example.com/user/'),
};
fakeAccountId = {
isThirdPartyUser: sinon.stub().returns(false),
username: sinon.stub().returns('albert'),
};
$imports.$mock({
'./account-id': fakeAccountId,
});
});
const fakeAnnotations = { const fakeAnnotations = {
withDisplayName: { withDisplayName: {
user: 'acct:albert@victoriana.com', user: 'acct:albert@victoriana.com',
...@@ -15,52 +46,130 @@ describe('sidebar/helpers/annotation-user', () => { ...@@ -15,52 +46,130 @@ describe('sidebar/helpers/annotation-user', () => {
}, },
}; };
[ describe('annotationDisplayName', () => {
{ context('annotation with first-party author', () => {
annotation: fakeAnnotations.withDisplayName, [
isThirdParty: false, {
isFeatureEnabled: false, annotation: fakeAnnotations.withDisplayName,
expected: 'albert', expected: 'albert',
}, },
{ {
annotation: fakeAnnotations.withDisplayName, annotation: fakeAnnotations.noDisplayName,
isThirdParty: true, expected: 'albert',
isFeatureEnabled: false, },
expected: 'Albert, Prince Consort', {
}, annotation: fakeAnnotations.noUserInfo,
{ expected: 'albert',
annotation: fakeAnnotations.withDisplayName, },
isThirdParty: false, ].forEach(testcase => {
isFeatureEnabled: true, it('should return author username if display-names feature flag is not enabled', () => {
expected: 'Albert, Prince Consort', fakeStore.isFeatureEnabled
}, .withArgs('client_display_names')
{ .returns(false);
annotation: fakeAnnotations.withDisplayName, assert.equal(
isThirdParty: true, annotationDisplayName(testcase.annotation, fakeStore),
isFeatureEnabled: true, testcase.expected
expected: 'Albert, Prince Consort', );
}, });
{ });
annotation: fakeAnnotations.noDisplayName,
isThirdParty: true, [
isFeatureEnabled: true, {
expected: 'albert', annotation: fakeAnnotations.withDisplayName,
}, expected: 'Albert, Prince Consort',
{ },
annotation: fakeAnnotations.noUserInfo, {
isThirdParty: true, annotation: fakeAnnotations.noDisplayName,
isFeatureEnabled: true, expected: 'albert',
expected: 'albert', },
}, {
].forEach(testCase => { annotation: fakeAnnotations.noUserInfo,
it('should return the appropriate author string for an annotation', () => { expected: 'albert',
},
].forEach(testcase => {
it('should return author display name when available if display-names feature flag is enabled', () => {
fakeStore.isFeatureEnabled
.withArgs('client_display_names')
.returns(true);
assert.equal(
annotationDisplayName(testcase.annotation, fakeStore),
testcase.expected
);
});
});
});
context('annotation with third-party author', () => {
[
{
annotation: fakeAnnotations.withDisplayName,
expected: 'Albert, Prince Consort',
},
{
annotation: fakeAnnotations.noDisplayName,
expected: 'albert',
},
{
annotation: fakeAnnotations.noUserInfo,
expected: 'albert',
},
].forEach(testcase => {
it('should return author display name if available', () => {
fakeAccountId.isThirdPartyUser.returns(true);
assert.equal(
annotationDisplayName(testcase.annotation, fakeStore),
testcase.expected
);
});
});
});
});
describe('annotationAuthorLink', () => {
it('should return a URL for first-party users', () => {
fakeAccountId.isThirdPartyUser.returns(false);
assert.equal(
annotationAuthorLink(
fakeAnnotations.withDisplayName,
fakeStore,
fakeSettings
),
'http://www.example.com/user/'
);
});
it('should return a URL for third-party users when configured with `usernameUrl`', () => {
fakeAccountId.isThirdPartyUser.returns(true);
assert.equal( assert.equal(
annotationDisplayName( annotationAuthorLink(
testCase.annotation, fakeAnnotations.withDisplayName,
testCase.isThirdParty, fakeStore,
testCase.isFeatureEnabled fakeSettings
),
'http://foo.bar/albert'
);
});
it('should not return a URL for third-party users if not configured with `usernameUrl`', () => {
fakeAccountId.isThirdPartyUser.returns(true);
assert.isUndefined(
annotationAuthorLink(fakeAnnotations.withDisplayName, fakeStore, {})
);
});
});
describe('annotationAuthorInfo', () => {
it('should return both a display name and a link for annotation author', () => {
assert.deepEqual(
annotationAuthorInfo(
fakeAnnotations.withDisplayName,
fakeStore,
fakeSettings
), ),
testCase.expected {
authorDisplayName: 'albert',
authorLink: 'http://www.example.com/user/',
}
); );
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment