Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
b1ce8485
Commit
b1ce8485
authored
Mar 12, 2021
by
Lyza Danger Gardner
Committed by
Lyza Gardner
Mar 15, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor AnnotationUser per conventions
Refactor AnnotationUser to be more "dumb" like other leafy components
parent
b328f7a8
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
123 additions
and
128 deletions
+123
-128
AnnotationHeader.js
src/sidebar/components/Annotation/AnnotationHeader.js
+41
-2
AnnotationUser.js
src/sidebar/components/Annotation/AnnotationUser.js
+6
-33
AnnotationHeader-test.js
...debar/components/Annotation/test/AnnotationHeader-test.js
+63
-0
AnnotationUser-test.js
...sidebar/components/Annotation/test/AnnotationUser-test.js
+13
-93
No files found.
src/sidebar/components/Annotation/AnnotationHeader.js
View file @
b1ce8485
import
{
SvgIcon
}
from
'@hypothesis/frontend-shared'
;
import
{
useMemo
}
from
'preact/hooks'
;
import
{
withServices
}
from
'../../service-context'
;
import
{
useStoreProxy
}
from
'../../store/use-store'
;
import
{
isThirdPartyUser
,
username
}
from
'../../helpers/account-id'
;
import
{
domainAndTitle
,
isHighlight
,
isReply
,
hasBeenEdited
,
}
from
'../../helpers/annotation-metadata'
;
import
{
annotationDisplayName
}
from
'../../helpers/annotation-user'
;
import
{
isPrivate
}
from
'../../helpers/permissions'
;
import
Button
from
'../Button'
;
...
...
@@ -19,6 +22,8 @@ import AnnotationUser from './AnnotationUser';
/**
* @typedef {import("../../../types/api").Annotation} Annotation
* @typedef {import('../../services/service-url').ServiceUrlGetter} ServiceUrlGetter
* @typedef {import('../../../types/config').MergedConfig} MergedConfig
*/
/**
...
...
@@ -27,6 +32,9 @@ import AnnotationUser from './AnnotationUser';
* @prop {boolean} [isEditing] - Whether the annotation is actively being edited
* @prop {number} replyCount - How many replies this annotation currently has
* @prop {boolean} threadIsCollapsed - Is this thread currently collapsed?
* @prop {ServiceUrlGetter} serviceUrl - Injected service
* @prop {MergedConfig} settings - Injected
*
*/
/**
...
...
@@ -36,13 +44,37 @@ import AnnotationUser from './AnnotationUser';
*
* @param {AnnotationHeaderProps} props
*/
export
default
function
AnnotationHeader
({
function
AnnotationHeader
({
annotation
,
isEditing
,
replyCount
,
threadIsCollapsed
,
serviceUrl
,
settings
,
})
{
const
store
=
useStoreProxy
();
const
authDomain
=
store
.
authDomain
();
const
displayNamesEnabled
=
store
.
isFeatureEnabled
(
'client_display_names'
);
const
isThirdParty
=
isThirdPartyUser
(
annotation
.
user
,
authDomain
);
const
authorDisplayName
=
annotationDisplayName
(
annotation
,
isThirdParty
,
displayNamesEnabled
);
const
authorLink
=
(()
=>
{
if
(
!
isThirdParty
)
{
return
serviceUrl
(
'user'
,
{
user
:
annotation
.
user
});
}
else
{
return
(
(
settings
.
usernameUrl
&&
`
${
settings
.
usernameUrl
}${
username
(
annotation
.
user
)}
`
)
??
undefined
);
}
})();
const
isCollapsedReply
=
isReply
(
annotation
)
&&
threadIsCollapsed
;
const
annotationIsPrivate
=
isPrivate
(
annotation
.
permissions
);
...
...
@@ -96,7 +128,10 @@ export default function AnnotationHeader({
title
=
"This annotation is visible only to you"
/>
)}
<
AnnotationUser
annotation
=
{
annotation
}
/
>
<
AnnotationUser
authorLink
=
{
authorLink
}
displayName
=
{
authorDisplayName
}
/
>
{
showReplyButton
&&
(
<
Button
className
=
"AnnotationHeader__reply-toggle"
...
...
@@ -143,3 +178,7 @@ export default function AnnotationHeader({
<
/header
>
);
}
AnnotationHeader
.
injectedProps
=
[
'serviceUrl'
,
'settings'
];
export
default
withServices
(
AnnotationHeader
);
src/sidebar/components/Annotation/AnnotationUser.js
View file @
b1ce8485
import
{
isThirdPartyUser
,
username
}
from
'../../helpers/account-id'
;
import
{
annotationDisplayName
}
from
'../../helpers/annotation-user'
;
import
{
withServices
}
from
'../../service-context'
;
/**
* @typedef {import("../../../types/api").Annotation} Annotation
* @typedef {import('../../../types/config').MergedConfig} MergedConfig
* @typedef {import('../../services/service-url').ServiceUrlGetter} ServiceUrlGetter
*/
/**
* @typedef AnnotationUserProps
* @prop {Annotation} annotation - The annotation whose user is relevant
* @prop {Object} features - Injected service
* @prop {ServiceUrlGetter} serviceUrl - Injected service
* @prop {MergedConfig} settings - Injected service
* @prop {string} [authorLink]
* @prop {string} displayName
*/
/**
...
...
@@ -22,30 +14,13 @@ import { withServices } from '../../service-context';
*
* @param {AnnotationUserProps} props
*/
function
AnnotationUser
({
annotation
,
features
,
serviceUrl
,
settings
})
{
const
user
=
annotation
.
user
;
const
isFirstPartyUser
=
!
isThirdPartyUser
(
user
,
settings
.
authDomain
);
const
username_
=
username
(
user
);
// How should the user's name be displayed?
const
displayName
=
annotationDisplayName
(
annotation
,
!
isFirstPartyUser
,
features
.
flagEnabled
(
'client_display_names'
)
);
const
shouldLinkToActivity
=
isFirstPartyUser
||
settings
.
usernameUrl
;
if
(
shouldLinkToActivity
)
{
function
AnnotationUser
({
authorLink
,
displayName
})
{
if
(
authorLink
)
{
return
(
<
div
className
=
"AnnotationUser"
>
<
a
className
=
"AnnotationUser__link"
href
=
{
isFirstPartyUser
?
serviceUrl
(
'user'
,
{
user
})
:
`
${
settings
.
usernameUrl
}${
username_
}
`
}
href
=
{
authorLink
}
target
=
"_blank"
rel
=
"noopener noreferrer"
>
...
...
@@ -62,6 +37,4 @@ function AnnotationUser({ annotation, features, serviceUrl, settings }) {
);
}
AnnotationUser
.
injectedProps
=
[
'features'
,
'serviceUrl'
,
'settings'
];
export
default
withServices
(
AnnotationUser
);
export
default
AnnotationUser
;
src/sidebar/components/Annotation/test/AnnotationHeader-test.js
View file @
b1ce8485
...
...
@@ -8,11 +8,15 @@ import mockImportedComponents from '../../../../test-util/mock-imported-componen
import
AnnotationHeader
,
{
$imports
}
from
'../AnnotationHeader'
;
describe
(
'AnnotationHeader'
,
()
=>
{
let
fakeAccountId
;
let
fakeAnnotationDisplayName
;
let
fakeDomainAndTitle
;
let
fakeIsHighlight
;
let
fakeIsReply
;
let
fakeHasBeenEdited
;
let
fakeIsPrivate
;
let
fakeServiceUrl
;
let
fakeSettings
;
let
fakeStore
;
const
createAnnotationHeader
=
props
=>
{
...
...
@@ -22,6 +26,8 @@ describe('AnnotationHeader', () => {
isEditing
=
{
false
}
replyCount
=
{
0
}
threadIsCollapsed
=
{
false
}
serviceUrl
=
{
fakeServiceUrl
}
settings
=
{
fakeSettings
}
{...
props
}
/
>
);
...
...
@@ -34,7 +40,19 @@ describe('AnnotationHeader', () => {
fakeHasBeenEdited
=
sinon
.
stub
().
returns
(
false
);
fakeIsPrivate
=
sinon
.
stub
();
fakeAccountId
=
{
isThirdPartyUser
:
sinon
.
stub
().
returns
(
false
),
username
:
sinon
.
stub
().
returnsArg
(
0
),
};
fakeAnnotationDisplayName
=
sinon
.
stub
().
returns
(
'Robbie Burns'
);
fakeServiceUrl
=
sinon
.
stub
().
returns
(
'http://example.com'
);
fakeSettings
=
{
usernameUrl
:
'http://foo.bar/'
};
fakeStore
=
{
authDomain
:
sinon
.
stub
().
returns
(
'foo.com'
),
isFeatureEnabled
:
sinon
.
stub
().
returns
(
false
),
route
:
sinon
.
stub
().
returns
(
'sidebar'
),
setExpanded
:
sinon
.
stub
(),
};
...
...
@@ -42,12 +60,16 @@ describe('AnnotationHeader', () => {
$imports
.
$mock
(
mockImportedComponents
());
$imports
.
$mock
({
'../../store/use-store'
:
{
useStoreProxy
:
()
=>
fakeStore
},
'../../helpers/account-id'
:
fakeAccountId
,
'../../helpers/annotation-metadata'
:
{
domainAndTitle
:
fakeDomainAndTitle
,
isHighlight
:
fakeIsHighlight
,
isReply
:
fakeIsReply
,
hasBeenEdited
:
fakeHasBeenEdited
,
},
'../../helpers/annotation-user'
:
{
annotationDisplayName
:
fakeAnnotationDisplayName
,
},
'../../helpers/permissions'
:
{
isPrivate
:
fakeIsPrivate
,
},
...
...
@@ -84,6 +106,47 @@ describe('AnnotationHeader', () => {
});
});
describe
(
'annotation author (user) information'
,
()
=>
{
it
(
'should link to author activity if first-party'
,
()
=>
{
fakeAccountId
.
isThirdPartyUser
.
returns
(
false
);
const
wrapper
=
createAnnotationHeader
();
assert
.
equal
(
wrapper
.
find
(
'AnnotationUser'
).
props
().
authorLink
,
'http://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'
,
()
=>
{
fakeAccountId
.
isThirdPartyUser
.
returns
(
true
);
const
wrapper
=
createAnnotationHeader
({
settings
:
{}
});
assert
.
isUndefined
(
wrapper
.
find
(
'AnnotationUser'
).
props
().
authorLink
);
});
it
(
'should pass the display name to AnnotationUser'
,
()
=>
{
const
wrapper
=
createAnnotationHeader
();
assert
.
equal
(
wrapper
.
find
(
'AnnotationUser'
).
props
().
displayName
,
'Robbie Burns'
);
});
});
describe
(
'expand replies toggle button'
,
()
=>
{
const
findReplyButton
=
wrapper
=>
wrapper
.
find
(
'Button'
).
filter
(
'.AnnotationHeader__reply-toggle'
);
...
...
src/sidebar/components/Annotation/test/AnnotationUser-test.js
View file @
b1ce8485
import
{
mount
}
from
'enzyme'
;
import
{
checkAccessibility
}
from
'../../../../test-util/accessibility'
;
import
mockImportedComponents
from
'../../../../test-util/mock-imported-components'
;
import
AnnotationUser
,
{
$imports
}
from
'../AnnotationUser'
;
import
AnnotationUser
from
'../AnnotationUser'
;
describe
(
'AnnotationUser'
,
()
=>
{
let
fakeAnnotation
;
let
fakeAnnotationUser
;
let
fakeFeatures
;
let
fakeIsThirdPartyUser
;
let
fakeServiceUrl
;
let
fakeSettings
;
let
fakeUsername
;
const
createAnnotationUser
=
()
=>
{
return
mount
(
<
AnnotationUser
annotation
=
{
fakeAnnotation
}
features
=
{
fakeFeatures
}
serviceUrl
=
{
fakeServiceUrl
}
settings
=
{
fakeSettings
}
/
>
);
};
beforeEach
(()
=>
{
fakeAnnotation
=
{
user
:
'someone@hypothes.is'
,
};
fakeAnnotationUser
=
{
annotationDisplayName
:
sinon
.
stub
()
.
callsFake
(
annotation
=>
annotation
.
user
),
const
createAnnotationUser
=
props
=>
{
return
mount
(
<
AnnotationUser
displayName
=
"Filbert Bronzo"
{...
props
}
/>
)
;
};
fakeFeatures
=
{
flagEnabled
:
sinon
.
stub
()
};
fakeIsThirdPartyUser
=
sinon
.
stub
().
returns
(
false
);
fakeServiceUrl
=
sinon
.
stub
();
fakeSettings
=
{};
fakeUsername
=
sinon
.
stub
();
$imports
.
$mock
(
mockImportedComponents
());
$imports
.
$mock
({
'../../helpers/account-id'
:
{
isThirdPartyUser
:
fakeIsThirdPartyUser
,
username
:
fakeUsername
,
},
'../../helpers/annotation-user'
:
fakeAnnotationUser
,
});
it
(
'links to the author activity page if link provided'
,
()
=>
{
const
wrapper
=
createAnnotationUser
({
authorLink
:
'http://www.foo.bar/baz'
,
});
afterEach
(()
=>
{
$imports
.
$restore
();
assert
.
isTrue
(
wrapper
.
find
(
'a[href="http://www.foo.bar/baz"]'
).
exists
());
});
describe
(
'link to user activity'
,
()
=>
{
context
(
'first-party user'
,
()
=>
{
it
(
'should provide a link to the user profile'
,
()
=>
{
fakeIsThirdPartyUser
.
returns
(
false
);
fakeServiceUrl
.
returns
(
'link-to-user'
);
it
(
'does not link to the author activity page if no link provided'
,
()
=>
{
const
wrapper
=
createAnnotationUser
();
const
linkEl
=
wrapper
.
find
(
'a'
);
assert
.
isOk
(
linkEl
.
exists
());
assert
.
calledWith
(
fakeServiceUrl
,
'user'
,
{
user
:
fakeAnnotation
.
user
,
});
assert
.
equal
(
linkEl
.
prop
(
'href'
),
'link-to-user'
);
});
});
context
(
'third-party user'
,
()
=>
{
beforeEach
(()
=>
{
fakeIsThirdPartyUser
.
returns
(
true
);
assert
.
isFalse
(
wrapper
.
find
(
'a'
).
exists
());
});
it
(
'should link to user if `settings.usernameUrl` is set'
,
()
=>
{
fakeSettings
.
usernameUrl
=
'http://example.com?user='
;
fakeUsername
.
returns
(
'elephant'
);
it
(
'renders the author name'
,
()
=>
{
const
wrapper
=
createAnnotationUser
();
const
linkEl
=
wrapper
.
find
(
'a'
);
assert
.
isOk
(
linkEl
.
exists
());
assert
.
equal
(
linkEl
.
prop
(
'href'
),
'http://example.com?user=elephant'
);
});
it
(
'should not link to user if `settings.usernameUrl` is not set'
,
()
=>
{
const
wrapper
=
createAnnotationUser
();
const
linkEl
=
wrapper
.
find
(
'a'
);
assert
.
isNotOk
(
linkEl
.
exists
());
});
});
});
it
(
'renders the annotation author name'
,
()
=>
{
fakeFeatures
.
flagEnabled
.
withArgs
(
'client_display_names'
).
returns
(
true
);
createAnnotationUser
();
assert
.
calledWith
(
fakeAnnotationUser
.
annotationDisplayName
,
fakeAnnotation
,
false
,
true
);
assert
.
equal
(
wrapper
.
text
(),
'Filbert Bronzo'
);
});
it
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment