mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-21 05:54:10 +08:00
MM-66627 (test): VH1-8 + EC Enzyme to RTL bulk migration (#35768)
* (test): vh1-8+EC enzyme to rtl bulk migration * address comments
This commit is contained in:
@@ -78,6 +78,7 @@ const intl = useIntl();
|
||||
- Add/extend RTL tests alongside the component (`*.test.tsx`). See `../tests/react_testing_utils.tsx` for helpers.
|
||||
- Prefer `userEvent` and accessible queries (`getByRole`) over implementation-specific selectors.
|
||||
- Avoid snapshots; assert visible behavior instead.
|
||||
- **Import Rule**: Never import directly from `@testing-library/react` or `react-dom/test-utils`. All testing utilities (`act`, `screen`, `fireEvent`, `waitFor`, `waitForElementToBeRemoved`, `userEvent`, `renderWithContext`, etc.) must be imported from `tests/react_testing_utils`, which re-exports everything from `@testing-library/react` alongside project-specific helpers.
|
||||
|
||||
## Icons
|
||||
- **Menu items and components should use Compass icon components** from `@mattermost/compass-icons/components` (e.g., `<DockWindowIcon size={18}/>`), not raw `<i className="icon icon-..."/>` elements.
|
||||
|
||||
@@ -1,578 +1,118 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/TextBox should match snapshot with additional, optional props 1`] = `
|
||||
<div
|
||||
className="textarea-wrapper textarea-wrapper-preview textarea-wrapper-preview--disabled"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="form-control custom-textarea textbox-preview-area"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyPress={[MockFunction]}
|
||||
tabIndex={0}
|
||||
class="textarea-wrapper textarea-wrapper-preview textarea-wrapper-preview--disabled"
|
||||
>
|
||||
<Connect(PostMarkdown)
|
||||
channelId="channelId"
|
||||
imageProps={
|
||||
Object {
|
||||
"hideUtilities": true,
|
||||
}
|
||||
}
|
||||
message="some test text"
|
||||
/>
|
||||
<div
|
||||
class="form-control custom-textarea textbox-preview-area"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="post-markdown"
|
||||
>
|
||||
some test text
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
class="form-control custom-textarea textbox-edit-area custom-textarea--emoji-picker bad-connection"
|
||||
data-testid="suggestion-box"
|
||||
disabled=""
|
||||
id="someid"
|
||||
placeholder="placeholder text"
|
||||
readonly=""
|
||||
style="visibility: hidden;"
|
||||
>
|
||||
some test text
|
||||
</textarea>
|
||||
</div>
|
||||
<Connect(SuggestionBox)
|
||||
className="form-control custom-textarea textbox-edit-area custom-textarea--emoji-picker bad-connection"
|
||||
contextId="channelId"
|
||||
disabled={true}
|
||||
id="someid"
|
||||
inputComponent={
|
||||
Object {
|
||||
"$$typeof": Symbol(react.memo),
|
||||
"compare": null,
|
||||
"type": Object {
|
||||
"$$typeof": Symbol(react.forward_ref),
|
||||
"propTypes": Object {
|
||||
"className": [Function],
|
||||
"defaultValue": [Function],
|
||||
"disabled": [Function],
|
||||
"id": [Function],
|
||||
"onChange": [Function],
|
||||
"onHeightChange": [Function],
|
||||
"onInput": [Function],
|
||||
"onWidthChange": [Function],
|
||||
"placeholder": [Function],
|
||||
"value": [Function],
|
||||
},
|
||||
"render": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
listComponent={[Function]}
|
||||
listPosition="top"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onComposition={[MockFunction]}
|
||||
onHeightChange={[MockFunction]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyPress={[MockFunction]}
|
||||
onKeyUp={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
openWhenEmpty={true}
|
||||
placeholder="placeholder text"
|
||||
providers={
|
||||
Array [
|
||||
AtMentionProvider {
|
||||
"addLastViewAtToProfiles": [Function],
|
||||
"autocompleteGroups": Array [
|
||||
Object {
|
||||
"allow_reference": true,
|
||||
"create_at": 1,
|
||||
"delete_at": 0,
|
||||
"description": "",
|
||||
"display_name": "group_display_name",
|
||||
"has_syncables": false,
|
||||
"id": "gid1",
|
||||
"member_count": 0,
|
||||
"name": "group_name",
|
||||
"remote_id": "",
|
||||
"scheme_admin": false,
|
||||
"source": "",
|
||||
"update_at": 1,
|
||||
},
|
||||
Object {
|
||||
"allow_reference": true,
|
||||
"create_at": 1,
|
||||
"delete_at": 0,
|
||||
"description": "",
|
||||
"display_name": "group_display_name",
|
||||
"has_syncables": false,
|
||||
"id": "gid2",
|
||||
"member_count": 0,
|
||||
"name": "group_name",
|
||||
"remote_id": "",
|
||||
"scheme_admin": false,
|
||||
"source": "",
|
||||
"update_at": 1,
|
||||
},
|
||||
],
|
||||
"autocompleteUsersInChannel": [Function],
|
||||
"channelId": "channelId",
|
||||
"currentUserId": "currentUserId",
|
||||
"data": null,
|
||||
"defaultAgent": undefined,
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"getProfilesInChannel": [Function],
|
||||
"lastCompletedWord": "",
|
||||
"lastPrefixWithNoResults": "",
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"priorityProfiles": undefined,
|
||||
"requestStarted": false,
|
||||
"searchAssociatedGroupsForReference": [Function],
|
||||
"triggerCharacter": "@",
|
||||
"useChannelMentions": true,
|
||||
},
|
||||
ChannelMentionProvider {
|
||||
"autocompleteChannels": [MockFunction],
|
||||
"delayChannelAutocomplete": false,
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"lastCompletedWord": "",
|
||||
"lastPrefixTrimmed": "",
|
||||
"lastPrefixWithNoResults": "",
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"requestStarted": false,
|
||||
"triggerCharacter": "~",
|
||||
},
|
||||
EmoticonProvider {
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"requestStarted": false,
|
||||
"triggerCharacter": ":",
|
||||
},
|
||||
]
|
||||
}
|
||||
spellCheck="true"
|
||||
style={
|
||||
Object {
|
||||
"visibility": "hidden",
|
||||
}
|
||||
}
|
||||
value="some test text"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/TextBox should match snapshot with required props 1`] = `
|
||||
<div
|
||||
className="textarea-wrapper"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="form-control custom-textarea textbox-preview-area"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyPress={[MockFunction]}
|
||||
tabIndex={0}
|
||||
class="textarea-wrapper"
|
||||
>
|
||||
<Connect(PostMarkdown)
|
||||
channelId="channelId"
|
||||
imageProps={
|
||||
Object {
|
||||
"hideUtilities": true,
|
||||
}
|
||||
}
|
||||
message="some test text"
|
||||
/>
|
||||
<div
|
||||
class="form-control custom-textarea textbox-preview-area"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="post-markdown"
|
||||
>
|
||||
some test text
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
class="form-control custom-textarea textbox-edit-area"
|
||||
data-testid="suggestion-box"
|
||||
id="someid"
|
||||
placeholder="placeholder text"
|
||||
readonly=""
|
||||
style="visibility: visible;"
|
||||
>
|
||||
some test text
|
||||
</textarea>
|
||||
</div>
|
||||
<Connect(SuggestionBox)
|
||||
className="form-control custom-textarea textbox-edit-area"
|
||||
contextId="channelId"
|
||||
id="someid"
|
||||
inputComponent={
|
||||
Object {
|
||||
"$$typeof": Symbol(react.memo),
|
||||
"compare": null,
|
||||
"type": Object {
|
||||
"$$typeof": Symbol(react.forward_ref),
|
||||
"propTypes": Object {
|
||||
"className": [Function],
|
||||
"defaultValue": [Function],
|
||||
"disabled": [Function],
|
||||
"id": [Function],
|
||||
"onChange": [Function],
|
||||
"onHeightChange": [Function],
|
||||
"onInput": [Function],
|
||||
"onWidthChange": [Function],
|
||||
"placeholder": [Function],
|
||||
"value": [Function],
|
||||
},
|
||||
"render": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
listComponent={[Function]}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyPress={[MockFunction]}
|
||||
onKeyUp={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
placeholder="placeholder text"
|
||||
providers={
|
||||
Array [
|
||||
AtMentionProvider {
|
||||
"addLastViewAtToProfiles": [Function],
|
||||
"autocompleteGroups": Array [
|
||||
Object {
|
||||
"allow_reference": true,
|
||||
"create_at": 1,
|
||||
"delete_at": 0,
|
||||
"description": "",
|
||||
"display_name": "group_display_name",
|
||||
"has_syncables": false,
|
||||
"id": "gid1",
|
||||
"member_count": 0,
|
||||
"name": "group_name",
|
||||
"remote_id": "",
|
||||
"scheme_admin": false,
|
||||
"source": "",
|
||||
"update_at": 1,
|
||||
},
|
||||
Object {
|
||||
"allow_reference": true,
|
||||
"create_at": 1,
|
||||
"delete_at": 0,
|
||||
"description": "",
|
||||
"display_name": "group_display_name",
|
||||
"has_syncables": false,
|
||||
"id": "gid2",
|
||||
"member_count": 0,
|
||||
"name": "group_name",
|
||||
"remote_id": "",
|
||||
"scheme_admin": false,
|
||||
"source": "",
|
||||
"update_at": 1,
|
||||
},
|
||||
],
|
||||
"autocompleteUsersInChannel": [Function],
|
||||
"channelId": "channelId",
|
||||
"currentUserId": "currentUserId",
|
||||
"data": null,
|
||||
"defaultAgent": undefined,
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"getProfilesInChannel": [Function],
|
||||
"lastCompletedWord": "",
|
||||
"lastPrefixWithNoResults": "",
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"priorityProfiles": undefined,
|
||||
"requestStarted": false,
|
||||
"searchAssociatedGroupsForReference": [Function],
|
||||
"triggerCharacter": "@",
|
||||
"useChannelMentions": true,
|
||||
},
|
||||
ChannelMentionProvider {
|
||||
"autocompleteChannels": [MockFunction],
|
||||
"delayChannelAutocomplete": false,
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"lastCompletedWord": "",
|
||||
"lastPrefixTrimmed": "",
|
||||
"lastPrefixWithNoResults": "",
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"requestStarted": false,
|
||||
"triggerCharacter": "~",
|
||||
},
|
||||
EmoticonProvider {
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"requestStarted": false,
|
||||
"triggerCharacter": ":",
|
||||
},
|
||||
]
|
||||
}
|
||||
spellCheck="true"
|
||||
style={
|
||||
Object {
|
||||
"visibility": "visible",
|
||||
}
|
||||
}
|
||||
value="some test text"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/TextBox should throw error when new property is too long 1`] = `
|
||||
<div
|
||||
className="textarea-wrapper"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="form-control custom-textarea textbox-preview-area"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyPress={[MockFunction]}
|
||||
tabIndex={0}
|
||||
class="textarea-wrapper"
|
||||
>
|
||||
<Connect(PostMarkdown)
|
||||
channelId="channelId"
|
||||
imageProps={
|
||||
Object {
|
||||
"hideUtilities": true,
|
||||
}
|
||||
}
|
||||
message="some test text that exceeds char limit"
|
||||
/>
|
||||
<div
|
||||
class="form-control custom-textarea textbox-preview-area"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="post-markdown"
|
||||
>
|
||||
some test text that exceeds char limit
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
class="form-control custom-textarea textbox-edit-area"
|
||||
data-testid="suggestion-box"
|
||||
id="someid"
|
||||
placeholder="placeholder text"
|
||||
readonly=""
|
||||
style="visibility: visible;"
|
||||
>
|
||||
some test text that exceeds char limit
|
||||
</textarea>
|
||||
</div>
|
||||
<Connect(SuggestionBox)
|
||||
className="form-control custom-textarea textbox-edit-area"
|
||||
contextId="channelId"
|
||||
id="someid"
|
||||
inputComponent={
|
||||
Object {
|
||||
"$$typeof": Symbol(react.memo),
|
||||
"compare": null,
|
||||
"type": Object {
|
||||
"$$typeof": Symbol(react.forward_ref),
|
||||
"propTypes": Object {
|
||||
"className": [Function],
|
||||
"defaultValue": [Function],
|
||||
"disabled": [Function],
|
||||
"id": [Function],
|
||||
"onChange": [Function],
|
||||
"onHeightChange": [Function],
|
||||
"onInput": [Function],
|
||||
"onWidthChange": [Function],
|
||||
"placeholder": [Function],
|
||||
"value": [Function],
|
||||
},
|
||||
"render": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
listComponent={[Function]}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyPress={[MockFunction]}
|
||||
onKeyUp={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
placeholder="placeholder text"
|
||||
providers={
|
||||
Array [
|
||||
AtMentionProvider {
|
||||
"addLastViewAtToProfiles": [Function],
|
||||
"autocompleteGroups": Array [
|
||||
Object {
|
||||
"allow_reference": true,
|
||||
"create_at": 1,
|
||||
"delete_at": 0,
|
||||
"description": "",
|
||||
"display_name": "group_display_name",
|
||||
"has_syncables": false,
|
||||
"id": "gid1",
|
||||
"member_count": 0,
|
||||
"name": "group_name",
|
||||
"remote_id": "",
|
||||
"scheme_admin": false,
|
||||
"source": "",
|
||||
"update_at": 1,
|
||||
},
|
||||
Object {
|
||||
"allow_reference": true,
|
||||
"create_at": 1,
|
||||
"delete_at": 0,
|
||||
"description": "",
|
||||
"display_name": "group_display_name",
|
||||
"has_syncables": false,
|
||||
"id": "gid2",
|
||||
"member_count": 0,
|
||||
"name": "group_name",
|
||||
"remote_id": "",
|
||||
"scheme_admin": false,
|
||||
"source": "",
|
||||
"update_at": 1,
|
||||
},
|
||||
],
|
||||
"autocompleteUsersInChannel": [Function],
|
||||
"channelId": "channelId",
|
||||
"currentUserId": "currentUserId",
|
||||
"data": null,
|
||||
"defaultAgent": undefined,
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"getProfilesInChannel": [Function],
|
||||
"lastCompletedWord": "",
|
||||
"lastPrefixWithNoResults": "",
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"priorityProfiles": undefined,
|
||||
"requestStarted": false,
|
||||
"searchAssociatedGroupsForReference": [Function],
|
||||
"triggerCharacter": "@",
|
||||
"useChannelMentions": true,
|
||||
},
|
||||
ChannelMentionProvider {
|
||||
"autocompleteChannels": [MockFunction],
|
||||
"delayChannelAutocomplete": false,
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"lastCompletedWord": "",
|
||||
"lastPrefixTrimmed": "",
|
||||
"lastPrefixWithNoResults": "",
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"requestStarted": false,
|
||||
"triggerCharacter": "~",
|
||||
},
|
||||
EmoticonProvider {
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"requestStarted": false,
|
||||
"triggerCharacter": ":",
|
||||
},
|
||||
]
|
||||
}
|
||||
spellCheck="true"
|
||||
style={
|
||||
Object {
|
||||
"visibility": "visible",
|
||||
}
|
||||
}
|
||||
value="some test text that exceeds char limit"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/TextBox should throw error when value is too long 1`] = `
|
||||
<div
|
||||
className="textarea-wrapper"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="form-control custom-textarea textbox-preview-area"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyPress={[MockFunction]}
|
||||
tabIndex={0}
|
||||
class="textarea-wrapper"
|
||||
>
|
||||
<Connect(PostMarkdown)
|
||||
channelId="channelId"
|
||||
imageProps={
|
||||
Object {
|
||||
"hideUtilities": true,
|
||||
}
|
||||
}
|
||||
message="some test text that exceeds char limit"
|
||||
/>
|
||||
<div
|
||||
class="form-control custom-textarea textbox-preview-area"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="post-markdown"
|
||||
>
|
||||
some test text that exceeds char limit
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
class="form-control custom-textarea textbox-edit-area"
|
||||
data-testid="suggestion-box"
|
||||
id="someid"
|
||||
placeholder="placeholder text"
|
||||
readonly=""
|
||||
style="visibility: visible;"
|
||||
>
|
||||
some test text that exceeds char limit
|
||||
</textarea>
|
||||
</div>
|
||||
<Connect(SuggestionBox)
|
||||
className="form-control custom-textarea textbox-edit-area"
|
||||
contextId="channelId"
|
||||
id="someid"
|
||||
inputComponent={
|
||||
Object {
|
||||
"$$typeof": Symbol(react.memo),
|
||||
"compare": null,
|
||||
"type": Object {
|
||||
"$$typeof": Symbol(react.forward_ref),
|
||||
"propTypes": Object {
|
||||
"className": [Function],
|
||||
"defaultValue": [Function],
|
||||
"disabled": [Function],
|
||||
"id": [Function],
|
||||
"onChange": [Function],
|
||||
"onHeightChange": [Function],
|
||||
"onInput": [Function],
|
||||
"onWidthChange": [Function],
|
||||
"placeholder": [Function],
|
||||
"value": [Function],
|
||||
},
|
||||
"render": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
listComponent={[Function]}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyPress={[MockFunction]}
|
||||
onKeyUp={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
placeholder="placeholder text"
|
||||
providers={
|
||||
Array [
|
||||
AtMentionProvider {
|
||||
"addLastViewAtToProfiles": [Function],
|
||||
"autocompleteGroups": Array [
|
||||
Object {
|
||||
"allow_reference": true,
|
||||
"create_at": 1,
|
||||
"delete_at": 0,
|
||||
"description": "",
|
||||
"display_name": "group_display_name",
|
||||
"has_syncables": false,
|
||||
"id": "gid1",
|
||||
"member_count": 0,
|
||||
"name": "group_name",
|
||||
"remote_id": "",
|
||||
"scheme_admin": false,
|
||||
"source": "",
|
||||
"update_at": 1,
|
||||
},
|
||||
Object {
|
||||
"allow_reference": true,
|
||||
"create_at": 1,
|
||||
"delete_at": 0,
|
||||
"description": "",
|
||||
"display_name": "group_display_name",
|
||||
"has_syncables": false,
|
||||
"id": "gid2",
|
||||
"member_count": 0,
|
||||
"name": "group_name",
|
||||
"remote_id": "",
|
||||
"scheme_admin": false,
|
||||
"source": "",
|
||||
"update_at": 1,
|
||||
},
|
||||
],
|
||||
"autocompleteUsersInChannel": [Function],
|
||||
"channelId": "channelId",
|
||||
"currentUserId": "currentUserId",
|
||||
"data": null,
|
||||
"defaultAgent": undefined,
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"getProfilesInChannel": [Function],
|
||||
"lastCompletedWord": "",
|
||||
"lastPrefixWithNoResults": "",
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"priorityProfiles": undefined,
|
||||
"requestStarted": false,
|
||||
"searchAssociatedGroupsForReference": [Function],
|
||||
"triggerCharacter": "@",
|
||||
"useChannelMentions": true,
|
||||
},
|
||||
ChannelMentionProvider {
|
||||
"autocompleteChannels": [MockFunction],
|
||||
"delayChannelAutocomplete": false,
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"lastCompletedWord": "",
|
||||
"lastPrefixTrimmed": "",
|
||||
"lastPrefixWithNoResults": "",
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"requestStarted": false,
|
||||
"triggerCharacter": "~",
|
||||
},
|
||||
EmoticonProvider {
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"requestStarted": false,
|
||||
"triggerCharacter": ":",
|
||||
},
|
||||
]
|
||||
}
|
||||
spellCheck="true"
|
||||
style={
|
||||
Object {
|
||||
"visibility": "visible",
|
||||
}
|
||||
}
|
||||
value="some test text that exceeds char limit"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
+87
-118
@@ -1,128 +1,97 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/AddUserToChannelModal should match snapshot 1`] = `
|
||||
<Modal
|
||||
animation={true}
|
||||
aria-labelledby="addChannelModalLabel"
|
||||
autoFocus={true}
|
||||
backdrop={true}
|
||||
bsClass="modal"
|
||||
dialogClassName="a11y__modal modal--overflow"
|
||||
dialogComponentClass={[Function]}
|
||||
enforceFocus={true}
|
||||
keyboard={true}
|
||||
manager={
|
||||
ModalManager {
|
||||
"add": [Function],
|
||||
"containers": Array [],
|
||||
"data": Array [],
|
||||
"handleContainerOverflow": true,
|
||||
"hideSiblingNodes": true,
|
||||
"isTopModal": [Function],
|
||||
"modals": Array [],
|
||||
"remove": [Function],
|
||||
}
|
||||
}
|
||||
onExited={[Function]}
|
||||
onHide={[Function]}
|
||||
renderBackdrop={[Function]}
|
||||
restoreFocus={true}
|
||||
role="none"
|
||||
show={true}
|
||||
<body
|
||||
class="modal-open"
|
||||
style="padding-right: 0px;"
|
||||
>
|
||||
<ModalHeader
|
||||
bsClass="modal-header"
|
||||
closeButton={true}
|
||||
closeLabel="Close"
|
||||
<div
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
role="none"
|
||||
>
|
||||
<ModalTitle
|
||||
bsClass="modal-title"
|
||||
componentClass="h1"
|
||||
id="addChannelModalLabel"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Add {name} to a Channel"
|
||||
id="add_user_to_channel_modal.title"
|
||||
values={
|
||||
Object {
|
||||
"name": "Fake Person",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</ModalTitle>
|
||||
</ModalHeader>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
role="form"
|
||||
>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
componentClass="div"
|
||||
<div
|
||||
class="fade modal-backdrop in"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="addChannelModalLabel"
|
||||
class="fade in modal"
|
||||
role="dialog"
|
||||
style="display: block; padding-right: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal__hint"
|
||||
class="a11y__modal modal--overflow modal-dialog"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Type to find a channel. Use ↑↓ to browse, ↵ to select, ESC to dismiss."
|
||||
id="add_user_to_channel_modal.help"
|
||||
/>
|
||||
<div
|
||||
class="modal-content"
|
||||
role="document"
|
||||
>
|
||||
<div
|
||||
class="modal-header"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
<h1
|
||||
class="modal-title"
|
||||
id="addChannelModalLabel"
|
||||
>
|
||||
Add Fake Person to a Channel
|
||||
</h1>
|
||||
</div>
|
||||
<form
|
||||
role="form"
|
||||
>
|
||||
<div
|
||||
class="modal-body"
|
||||
>
|
||||
<div
|
||||
class="modal__hint"
|
||||
>
|
||||
Type to find a channel. Use ↑↓ to browse, ↵ to select, ESC to dismiss.
|
||||
</div>
|
||||
<div
|
||||
class="pos-relative"
|
||||
/>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="modal-footer"
|
||||
>
|
||||
<button
|
||||
class="btn btn-tertiary"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
disabled=""
|
||||
id="add-user-to-channel-modal__add-button"
|
||||
type="button"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pos-relative"
|
||||
>
|
||||
<Connect(SuggestionBox)
|
||||
className="form-control focused"
|
||||
completeOnTab={false}
|
||||
listComponent={[Function]}
|
||||
listPosition="bottom"
|
||||
maxLength="64"
|
||||
onChange={[Function]}
|
||||
onItemSelected={[Function]}
|
||||
openWhenEmpty={false}
|
||||
providers={
|
||||
Array [
|
||||
SearchChannelWithPermissionsProvider {
|
||||
"autocompleteChannelsForSearch": [MockFunction],
|
||||
"disableDispatches": false,
|
||||
"forceDispatch": false,
|
||||
"latestComplete": true,
|
||||
"latestPrefix": "",
|
||||
"requestStarted": false,
|
||||
},
|
||||
]
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div />
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
bsClass="modal-footer"
|
||||
componentClass="div"
|
||||
>
|
||||
<button
|
||||
className="btn btn-tertiary"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="add_user_to_channel_modal.cancel"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
disabled={true}
|
||||
id="add-user-to-channel-modal__add-button"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Add"
|
||||
id="add_user_to_channel_modal.add"
|
||||
/>
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
+208
-99
@@ -1,13 +1,35 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import AddUserToChannelModal from 'components/add_user_to_channel_modal/add_user_to_channel_modal';
|
||||
|
||||
import {act, renderWithContext, userEvent, waitFor} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
let mockOnItemSelected: ((selection: any) => void) | undefined;
|
||||
|
||||
jest.mock('components/suggestion/suggestion_box', () => {
|
||||
const react = require('react');
|
||||
return {
|
||||
__esModule: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- forwardRef arity; ref unused in mock
|
||||
default: react.forwardRef((props: any, _ref: React.Ref<unknown>) => {
|
||||
mockOnItemSelected = props.onItemSelected;
|
||||
return null;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('components/suggestion/search_channel_with_permissions_provider', () =>
|
||||
jest.fn().mockImplementation(() => ({disableDispatches: false})),
|
||||
);
|
||||
|
||||
function getAddButton() {
|
||||
return document.querySelector('#add-user-to-channel-modal__add-button') as HTMLButtonElement;
|
||||
}
|
||||
|
||||
describe('components/AddUserToChannelModal', () => {
|
||||
const baseProps = {
|
||||
channelMembers: {},
|
||||
@@ -24,52 +46,101 @@ describe('components/AddUserToChannelModal', () => {
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnItemSelected = undefined;
|
||||
});
|
||||
|
||||
async function selectChannel(channelId = 'someChannelId', displayName = 'channelName') {
|
||||
await act(async () => {
|
||||
mockOnItemSelected!({channel: TestHelper.getChannelMock({id: channelId, display_name: displayName})});
|
||||
});
|
||||
}
|
||||
|
||||
it('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const {baseElement} = renderWithContext(
|
||||
<AddUserToChannelModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('#add-user-to-channel-modal__add-button').props().disabled).toBe(true);
|
||||
expect(wrapper.find('#add-user-to-channel-modal__user-is-member').exists()).toBe(false);
|
||||
expect(wrapper.find('#add-user-to-channel-modal__invite-error').exists()).toBe(false);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(getAddButton().disabled).toBe(true);
|
||||
expect(document.querySelector('#add-user-to-channel-modal__user-is-member')).toBeNull();
|
||||
expect(document.querySelector('#add-user-to-channel-modal__invite-error')).toBeNull();
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should enable the add button when a channel is selected', () => {
|
||||
const wrapper = shallow(
|
||||
it('should enable the add button when a channel is selected', async () => {
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({selectedChannelId: 'someChannelId'});
|
||||
expect(wrapper.find('#add-user-to-channel-modal__add-button').props().disabled).toBe(false);
|
||||
expect(wrapper.find('#add-user-to-channel-modal__invite-error').exists()).toBe(false);
|
||||
await selectChannel();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getAddButton().disabled).toBe(false);
|
||||
});
|
||||
expect(document.querySelector('#add-user-to-channel-modal__invite-error')).toBeNull();
|
||||
});
|
||||
|
||||
it('should show invite error when an error message is captured', () => {
|
||||
const wrapper = shallow(
|
||||
<AddUserToChannelModal {...baseProps}/>,
|
||||
it('should show invite error when an error message is captured', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {
|
||||
...baseProps.actions,
|
||||
addChannelMember: jest.fn().mockResolvedValue({error: {message: 'some error'}}),
|
||||
},
|
||||
};
|
||||
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({submitError: 'some error'});
|
||||
expect(wrapper.find('#add-user-to-channel-modal__add-button').props().disabled).toBe(true);
|
||||
expect(wrapper.find('#add-user-to-channel-modal__invite-error').exists()).toBe(true);
|
||||
});
|
||||
await selectChannel();
|
||||
|
||||
it('should disable add button when membership is being checked', () => {
|
||||
const wrapper = shallow(
|
||||
<AddUserToChannelModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({
|
||||
selectedChannelId: 'someChannelId',
|
||||
checkingForMembership: true,
|
||||
await waitFor(() => {
|
||||
expect(getAddButton().disabled).toBe(false);
|
||||
});
|
||||
|
||||
expect(wrapper.find('#add-user-to-channel-modal__add-button').props().disabled).toBe(true);
|
||||
await userEvent.click(getAddButton());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector('#add-user-to-channel-modal__invite-error')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should display error message if user is a member of the selected channel', () => {
|
||||
const props = {...baseProps,
|
||||
it('should disable add button when membership is being checked', async () => {
|
||||
let resolveGetChannelMember: (value: any) => void;
|
||||
const getChannelMemberPromise = new Promise<Record<string, never>>((resolve) => {
|
||||
resolveGetChannelMember = resolve;
|
||||
});
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {
|
||||
...baseProps.actions,
|
||||
getChannelMember: jest.fn(() => getChannelMemberPromise),
|
||||
},
|
||||
};
|
||||
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
// Select a channel - membership check starts
|
||||
act(() => {
|
||||
mockOnItemSelected!({channel: TestHelper.getChannelMock({id: 'someChannelId', display_name: 'channelName'})});
|
||||
});
|
||||
|
||||
// While checking membership, button should be disabled
|
||||
expect(getAddButton().disabled).toBe(true);
|
||||
|
||||
// Resolve the membership check
|
||||
await act(async () => {
|
||||
resolveGetChannelMember!({} as Record<string, never>);
|
||||
});
|
||||
});
|
||||
|
||||
it('should display error message if user is a member of the selected channel', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
channelMembers: {
|
||||
someChannelId: {
|
||||
someUserId: TestHelper.getChannelMembershipMock({}),
|
||||
@@ -77,37 +148,63 @@ describe('components/AddUserToChannelModal', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({selectedChannelId: 'someChannelId'});
|
||||
expect(wrapper.find('#add-user-to-channel-modal__add-button').props().disabled).toBe(true);
|
||||
expect(wrapper.find('#add-user-to-channel-modal__user-is-member').exists()).toBe(true);
|
||||
await selectChannel();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getAddButton().disabled).toBe(true);
|
||||
expect(document.querySelector('#add-user-to-channel-modal__user-is-member')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable the add button when saving', () => {
|
||||
const wrapper = shallow(
|
||||
<AddUserToChannelModal {...baseProps}/>,
|
||||
it('should disable the add button when saving', async () => {
|
||||
let resolveAddChannelMember: (value: any) => void;
|
||||
const addChannelMemberPromise = new Promise<Record<string, never>>((resolve) => {
|
||||
resolveAddChannelMember = resolve;
|
||||
});
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {
|
||||
...baseProps.actions,
|
||||
addChannelMember: jest.fn(() => addChannelMemberPromise),
|
||||
},
|
||||
};
|
||||
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({
|
||||
selectedChannelId: 'someChannelId',
|
||||
saving: true,
|
||||
await selectChannel();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getAddButton().disabled).toBe(false);
|
||||
});
|
||||
|
||||
await userEvent.click(getAddButton());
|
||||
|
||||
// While saving, button should be disabled
|
||||
expect(getAddButton().disabled).toBe(true);
|
||||
|
||||
// Clean up
|
||||
await act(async () => {
|
||||
resolveAddChannelMember!({} as Record<string, never>);
|
||||
});
|
||||
expect(wrapper.find('#add-user-to-channel-modal__add-button').props().disabled).toBe(true);
|
||||
});
|
||||
|
||||
describe('didSelectChannel', () => {
|
||||
it('should fetch the selected user\'s membership for the selected channel', () => {
|
||||
it('should fetch the selected user\'s membership for the selected channel', async () => {
|
||||
const props = {...baseProps};
|
||||
|
||||
const wrapper = shallow<AddUserToChannelModal>(
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
const selection = {channel: TestHelper.getChannelMock({id: 'someChannelId', display_name: 'channelName'})};
|
||||
wrapper.instance().didSelectChannel(selection);
|
||||
await selectChannel();
|
||||
|
||||
expect(props.actions.getChannelMember).toHaveBeenCalledWith('someChannelId', 'someUserId');
|
||||
});
|
||||
|
||||
@@ -123,45 +220,48 @@ describe('components/AddUserToChannelModal', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow<AddUserToChannelModal>(
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state().text).toEqual('');
|
||||
expect(wrapper.state().checkingForMembership).toEqual(false);
|
||||
expect(wrapper.state().selectedChannelId).toEqual(null);
|
||||
expect(wrapper.state().submitError).toEqual('');
|
||||
// Initially, add button should be disabled (no channel selected)
|
||||
expect(getAddButton().disabled).toBe(true);
|
||||
|
||||
const selection = {channel: TestHelper.getChannelMock({id: 'someChannelId', display_name: 'channelName'})};
|
||||
wrapper.setState({submitError: 'some pre-existing error'});
|
||||
// Select a channel
|
||||
act(() => {
|
||||
mockOnItemSelected!({channel: TestHelper.getChannelMock({id: 'someChannelId', display_name: 'channelName'})});
|
||||
});
|
||||
|
||||
wrapper.instance().didSelectChannel(selection);
|
||||
expect(wrapper.state().text).toEqual('channelName');
|
||||
expect(wrapper.state().checkingForMembership).toEqual(true);
|
||||
expect(wrapper.state().selectedChannelId).toEqual('someChannelId');
|
||||
expect(wrapper.state().submitError).toEqual('');
|
||||
// During membership check, button should be disabled
|
||||
expect(getAddButton().disabled).toBe(true);
|
||||
|
||||
await promise;
|
||||
expect(wrapper.state().checkingForMembership).toEqual(false);
|
||||
// Wait for membership check
|
||||
await act(async () => {
|
||||
await promise;
|
||||
});
|
||||
|
||||
// After membership check, button should be enabled
|
||||
await waitFor(() => {
|
||||
expect(getAddButton().disabled).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleSubmit', () => {
|
||||
it('should do nothing if no channel is selected', () => {
|
||||
it('should do nothing if no channel is selected', async () => {
|
||||
const props = {...baseProps};
|
||||
|
||||
const wrapper = shallow<AddUserToChannelModal>(
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
const event: any = {stopPropagation: jest.fn(), preventDefault: jest.fn()};
|
||||
wrapper.instance().handleSubmit(event);
|
||||
expect(wrapper.state().saving).toBe(false);
|
||||
await userEvent.click(getAddButton());
|
||||
expect(props.actions.addChannelMember).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should do nothing if user is a member of the selected channel', () => {
|
||||
const props = {...baseProps,
|
||||
it('should do nothing if user is a member of the selected channel', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
channelMembers: {
|
||||
someChannelId: {
|
||||
someUserId: TestHelper.getChannelMembershipMock({}),
|
||||
@@ -169,32 +269,35 @@ describe('components/AddUserToChannelModal', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow<AddUserToChannelModal>(
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({selectedChannelId: 'someChannelId'});
|
||||
const event: any = {stopPropagation: jest.fn(), preventDefault: jest.fn()};
|
||||
wrapper.instance().handleSubmit(event);
|
||||
expect(wrapper.state().saving).toBe(false);
|
||||
await selectChannel();
|
||||
|
||||
await userEvent.click(getAddButton());
|
||||
expect(props.actions.addChannelMember).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should submit if user is not a member of the selected channel', () => {
|
||||
const props = {...baseProps,
|
||||
it('should submit if user is not a member of the selected channel', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
channelMembers: {
|
||||
someChannelId: {},
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow<AddUserToChannelModal>(
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({selectedChannelId: 'someChannelId'});
|
||||
const event: any = {stopPropagation: jest.fn(), preventDefault: jest.fn()};
|
||||
wrapper.instance().handleSubmit(event);
|
||||
expect(wrapper.state().saving).toBe(true);
|
||||
await selectChannel();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getAddButton().disabled).toBe(false);
|
||||
});
|
||||
|
||||
await userEvent.click(getAddButton());
|
||||
expect(props.actions.addChannelMember).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -204,51 +307,57 @@ describe('components/AddUserToChannelModal', () => {
|
||||
...baseProps,
|
||||
actions: {
|
||||
...baseProps.actions,
|
||||
addChannelMember: () => promise,
|
||||
addChannelMember: jest.fn(() => promise),
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow<AddUserToChannelModal>(
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state().show).toBe(true);
|
||||
expect(wrapper.state().saving).toBe(false);
|
||||
wrapper.setState({selectedChannelId: 'someChannelId'});
|
||||
await selectChannel();
|
||||
|
||||
const event: any = {stopPropagation: jest.fn(), preventDefault: jest.fn()};
|
||||
wrapper.instance().handleSubmit(event);
|
||||
expect(wrapper.state().show).toBe(true);
|
||||
expect(wrapper.state().saving).toBe(true);
|
||||
await waitFor(() => {
|
||||
expect(getAddButton().disabled).toBe(false);
|
||||
});
|
||||
|
||||
await promise;
|
||||
expect(wrapper.state().submitError).toEqual('');
|
||||
expect(wrapper.state().show).toBe(false);
|
||||
await userEvent.click(getAddButton());
|
||||
|
||||
// After successful save, modal should close (no error)
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector('#add-user-to-channel-modal__invite-error')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
test('should match state when save fails', async () => {
|
||||
const promise = Promise.resolve({error: new Error('some error')});
|
||||
const errorMessage = 'some error';
|
||||
const promise = Promise.resolve({error: new Error(errorMessage)});
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {
|
||||
...baseProps.actions,
|
||||
addChannelMember: () => promise,
|
||||
addChannelMember: jest.fn(() => promise),
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow<AddUserToChannelModal>(
|
||||
renderWithContext(
|
||||
<AddUserToChannelModal {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state().show).toBe(true);
|
||||
wrapper.setState({selectedChannelId: 'someChannelId'});
|
||||
await selectChannel();
|
||||
|
||||
const event: any = {stopPropagation: jest.fn(), preventDefault: jest.fn()};
|
||||
wrapper.instance().handleSubmit(event);
|
||||
await waitFor(() => {
|
||||
expect(getAddButton().disabled).toBe(false);
|
||||
});
|
||||
|
||||
await promise;
|
||||
expect(wrapper.state().submitError).toEqual('some error');
|
||||
expect(wrapper.state().show).toBe(true);
|
||||
await userEvent.click(getAddButton());
|
||||
|
||||
// After failed save, error should be shown
|
||||
await waitFor(() => {
|
||||
const inviteError = document.querySelector('#add-user-to-channel-modal__invite-error');
|
||||
expect(inviteError).not.toBeNull();
|
||||
expect(inviteError).toHaveTextContent(errorMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+31
-15
@@ -7,9 +7,16 @@ import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import ManageTeamsModal from 'components/admin_console/manage_teams_modal/manage_teams_modal';
|
||||
|
||||
import {renderWithContext, screen, waitFor} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, runPostRenderAct, screen, waitFor} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
/**
|
||||
* `runPostRenderAct` rounds (each `await Promise.resolve()`): (1) passive effects run so `useEffect`
|
||||
* starts `loadTeamsAndTeamMembers`; (2) mocked `actions.getTeamMembersForUser` resolves →
|
||||
* `getTeamMembers` → `setTeamMembers`; (3) mocked `actions.getTeamsForUser` resolves → `setTeams`.
|
||||
*/
|
||||
const MANAGE_TEAMS_MODAL_ASYNC_ROUNDS = 3;
|
||||
|
||||
describe('ManageTeamsModal', () => {
|
||||
const baseProps = {
|
||||
locale: General.DEFAULT_LOCALE,
|
||||
@@ -35,31 +42,35 @@ describe('ManageTeamsModal', () => {
|
||||
});
|
||||
|
||||
test('should match snapshot init', async () => {
|
||||
const {baseElement} = renderWithContext(<ManageTeamsModal {...baseProps}/>);
|
||||
const {baseElement} = renderWithContext(
|
||||
<ManageTeamsModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(baseProps.actions.getTeamMembersForUser).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.getTeamsForUser).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
await runPostRenderAct(MANAGE_TEAMS_MODAL_ASYNC_ROUNDS);
|
||||
|
||||
expect(baseProps.actions.getTeamMembersForUser).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.getTeamsForUser).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(screen.getByText('Manage Teams')).toBeInTheDocument();
|
||||
expect(screen.getByText('@currentUsername')).toBeInTheDocument();
|
||||
expect(screen.getByText('currentUser@test.com')).toBeInTheDocument();
|
||||
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call api calls on mount', async () => {
|
||||
renderWithContext(<ManageTeamsModal {...baseProps}/>);
|
||||
renderWithContext(
|
||||
<ManageTeamsModal {...baseProps}/>,
|
||||
);
|
||||
await runPostRenderAct(MANAGE_TEAMS_MODAL_ASYNC_ROUNDS);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(baseProps.actions.getTeamMembersForUser).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.getTeamMembersForUser).toHaveBeenCalledWith(baseProps.user.id);
|
||||
expect(baseProps.actions.getTeamsForUser).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.getTeamsForUser).toHaveBeenCalledWith(baseProps.user.id);
|
||||
});
|
||||
expect(baseProps.actions.getTeamMembersForUser).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.getTeamMembersForUser).toHaveBeenCalledWith(baseProps.user.id);
|
||||
expect(baseProps.actions.getTeamsForUser).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.getTeamsForUser).toHaveBeenCalledWith(baseProps.user.id);
|
||||
});
|
||||
|
||||
test('should save data in state from api calls', async () => {
|
||||
test('should show team from api data after load', async () => {
|
||||
const mockTeamData = TestHelper.getTeamMock({
|
||||
id: '123test',
|
||||
name: 'testTeam',
|
||||
@@ -79,10 +90,15 @@ describe('ManageTeamsModal', () => {
|
||||
},
|
||||
};
|
||||
|
||||
renderWithContext(<ManageTeamsModal {...props}/>);
|
||||
renderWithContext(
|
||||
<ManageTeamsModal {...props}/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(mockTeamData.display_name)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Non-system-admin users get ManageTeamsDropdown; default role label for plain membership.
|
||||
expect(screen.getByText('Team Member')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {act} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import type {AppField} from '@mattermost/types/apps';
|
||||
@@ -218,69 +217,57 @@ describe('components/apps_form/apps_form_field/AppsFormField', () => {
|
||||
expect(reactSelect).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render user select field', async () => {
|
||||
it('should render user select field', () => {
|
||||
const userField: AppField = {
|
||||
name: 'user-field',
|
||||
type: AppFieldTypes.USER,
|
||||
label: 'User Field',
|
||||
};
|
||||
|
||||
let container: HTMLElement;
|
||||
await act(async () => {
|
||||
const result = renderWithContext(
|
||||
<AppsFormField
|
||||
{...baseProps}
|
||||
field={userField}
|
||||
/>,
|
||||
);
|
||||
container = result.container;
|
||||
});
|
||||
const {container} = renderWithContext(
|
||||
<AppsFormField
|
||||
{...baseProps}
|
||||
field={userField}
|
||||
/>,
|
||||
);
|
||||
|
||||
const reactSelect = container!.querySelector('.react-select');
|
||||
const reactSelect = container.querySelector('.react-select');
|
||||
expect(reactSelect).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render channel select field', async () => {
|
||||
it('should render channel select field', () => {
|
||||
const channelField: AppField = {
|
||||
name: 'channel-field',
|
||||
type: AppFieldTypes.CHANNEL,
|
||||
label: 'Channel Field',
|
||||
};
|
||||
|
||||
let container: HTMLElement;
|
||||
await act(async () => {
|
||||
const result = renderWithContext(
|
||||
<AppsFormField
|
||||
{...baseProps}
|
||||
field={channelField}
|
||||
/>,
|
||||
);
|
||||
container = result.container;
|
||||
});
|
||||
const {container} = renderWithContext(
|
||||
<AppsFormField
|
||||
{...baseProps}
|
||||
field={channelField}
|
||||
/>,
|
||||
);
|
||||
|
||||
const reactSelect = container!.querySelector('.react-select');
|
||||
const reactSelect = container.querySelector('.react-select');
|
||||
expect(reactSelect).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render dynamic select field', async () => {
|
||||
it('should render dynamic select field', () => {
|
||||
const dynamicSelectField: AppField = {
|
||||
name: 'dynamic-select-field',
|
||||
type: AppFieldTypes.DYNAMIC_SELECT,
|
||||
label: 'Dynamic Select Field',
|
||||
};
|
||||
|
||||
let container: HTMLElement;
|
||||
await act(async () => {
|
||||
const result = renderWithContext(
|
||||
<AppsFormField
|
||||
{...baseProps}
|
||||
field={dynamicSelectField}
|
||||
/>,
|
||||
);
|
||||
container = result.container;
|
||||
});
|
||||
const {container} = renderWithContext(
|
||||
<AppsFormField
|
||||
{...baseProps}
|
||||
field={dynamicSelectField}
|
||||
/>,
|
||||
);
|
||||
|
||||
const reactSelect = container!.querySelector('.react-select');
|
||||
const reactSelect = container.querySelector('.react-select');
|
||||
expect(reactSelect).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
+44
-33
@@ -1,40 +1,51 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`AudioVideoPreview should match snapshot without children 1`] = `
|
||||
<video
|
||||
controls={true}
|
||||
data-setup="{}"
|
||||
height={480}
|
||||
key="file_id"
|
||||
width={640}
|
||||
>
|
||||
<source
|
||||
src="/api/v4/files/file_id"
|
||||
/>
|
||||
</video>
|
||||
<div>
|
||||
<video
|
||||
controls=""
|
||||
data-setup="{}"
|
||||
height="480"
|
||||
width="640"
|
||||
>
|
||||
<source
|
||||
src="/api/v4/files/file_id"
|
||||
/>
|
||||
</video>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AudioVideoPreview should match snapshot, cannot play 1`] = `
|
||||
<Connect(Component)
|
||||
fileInfo={
|
||||
Object {
|
||||
"archived": false,
|
||||
"channel_id": "channel_id",
|
||||
"clientId": "client_id",
|
||||
"create_at": 1,
|
||||
"delete_at": 1,
|
||||
"extension": "mov",
|
||||
"has_preview_image": true,
|
||||
"height": 200,
|
||||
"id": "file_id",
|
||||
"mime_type": "mime_type",
|
||||
"name": "name",
|
||||
"size": 1,
|
||||
"update_at": 1,
|
||||
"user_id": "user_id",
|
||||
"width": 350,
|
||||
}
|
||||
}
|
||||
fileUrl="/api/v4/files/file_id"
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
class="file-details__container"
|
||||
>
|
||||
<a
|
||||
class="file-details__preview"
|
||||
href="/api/v4/files/file_id"
|
||||
>
|
||||
<span
|
||||
class="file-details__preview-helper"
|
||||
/>
|
||||
<img
|
||||
alt="file preview"
|
||||
src=""
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
class="file-details"
|
||||
>
|
||||
<div
|
||||
class="file-details__name"
|
||||
>
|
||||
name
|
||||
</div>
|
||||
<div
|
||||
class="file-details__info"
|
||||
>
|
||||
File type MOV, Size 1B
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {fireEvent, renderWithContext} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import AudioVideoPreview from './audio_video_preview';
|
||||
@@ -19,17 +19,21 @@ describe('AudioVideoPreview', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot without children', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<AudioVideoPreview {...baseProps}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, cannot play', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<AudioVideoPreview {...baseProps}/>,
|
||||
);
|
||||
wrapper.setState({canPlay: false});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
// Trigger the error handler to set canPlay to false; fireEvent.error dispatches the <source> error (media load failure).
|
||||
const source = container.querySelector('source')!;
|
||||
fireEvent.error(source);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -624,9 +624,7 @@ describe('components/channel_invite_modal', () => {
|
||||
profilesFromRecentDMs: [users[1]],
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
});
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
|
||||
// Wait for the API call to complete and state to update
|
||||
await act(async () => {
|
||||
@@ -657,9 +655,7 @@ describe('components/channel_invite_modal', () => {
|
||||
profilesFromRecentDMs: [users[1]],
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
});
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
await userEvent.type(input, 'user');
|
||||
@@ -687,11 +683,9 @@ describe('components/channel_invite_modal', () => {
|
||||
};
|
||||
|
||||
// Render the component
|
||||
await act(async () => {
|
||||
renderWithContext(
|
||||
<ChannelInviteModal {...props}/>,
|
||||
);
|
||||
});
|
||||
renderWithContext(
|
||||
<ChannelInviteModal {...props}/>,
|
||||
);
|
||||
|
||||
// Reset the mock after component mount to ignore initial data loading
|
||||
getProfilesNotInChannelMock.mockClear();
|
||||
@@ -770,9 +764,7 @@ describe('components/channel_invite_modal', () => {
|
||||
profilesNotInCurrentChannel: [users[0]],
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
});
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
await userEvent.type(input, '@');
|
||||
@@ -819,9 +811,7 @@ describe('components/channel_invite_modal', () => {
|
||||
profilesNotInCurrentChannel: [users[0]],
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
});
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
|
||||
// Wait for the API call to complete and state to update
|
||||
await act(async () => {
|
||||
@@ -853,9 +843,7 @@ describe('components/channel_invite_modal', () => {
|
||||
channel: {...channel, policy_enforced: true},
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
});
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
|
||||
// Wait for the API call to complete
|
||||
await act(async () => {
|
||||
@@ -886,9 +874,7 @@ describe('components/channel_invite_modal', () => {
|
||||
includeUsers: {[users[1].id]: users[1]}, // Contaminated data
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
});
|
||||
renderWithContext(<ChannelInviteModal {...props}/>);
|
||||
|
||||
// Wait for the API call to complete and state to update
|
||||
await act(async () => {
|
||||
|
||||
+20
-17
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import CenterChannel from './center_channel';
|
||||
|
||||
import type {OwnProps} from './index';
|
||||
@@ -29,21 +26,27 @@ describe('components/channel_layout/CenterChannel', () => {
|
||||
},
|
||||
};
|
||||
test('should call update returnTo on props change', () => {
|
||||
const wrapper = shallow(<CenterChannel {...props}/>);
|
||||
let state = {returnTo: '', lastReturnTo: ''};
|
||||
|
||||
expect(wrapper.state('returnTo')).toBe('');
|
||||
// Simulate initial render with pathname '/some'
|
||||
let derived = CenterChannel.getDerivedStateFromProps(props as any, state);
|
||||
state = {...state, ...derived};
|
||||
expect(state.returnTo).toBe('');
|
||||
|
||||
wrapper.setProps({
|
||||
location: {
|
||||
pathname: '/pl/path',
|
||||
},
|
||||
});
|
||||
expect(wrapper.state('returnTo')).toBe('/some');
|
||||
wrapper.setProps({
|
||||
location: {
|
||||
pathname: '/pl/path1',
|
||||
},
|
||||
});
|
||||
expect(wrapper.state('returnTo')).toBe('/pl/path');
|
||||
// Simulate props change to '/pl/path'
|
||||
derived = CenterChannel.getDerivedStateFromProps(
|
||||
{...props, location: {pathname: '/pl/path'}} as any,
|
||||
state,
|
||||
);
|
||||
state = {...state, ...derived};
|
||||
expect(state.returnTo).toBe('/some');
|
||||
|
||||
// Simulate props change to '/pl/path1'
|
||||
derived = CenterChannel.getDerivedStateFromProps(
|
||||
{...props, location: {pathname: '/pl/path1'}} as any,
|
||||
state,
|
||||
);
|
||||
state = {...state, ...derived};
|
||||
expect(state.returnTo).toBe('/pl/path');
|
||||
});
|
||||
});
|
||||
|
||||
+24
-19
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import {render} from '@testing-library/react';
|
||||
import type {History} from 'history';
|
||||
import React from 'react';
|
||||
|
||||
@@ -9,11 +9,20 @@ import {getHistory} from 'utils/browser_history';
|
||||
|
||||
import ChannelIdentifierRouter from './channel_identifier_router';
|
||||
|
||||
jest.mock('components/channel_view/index', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}));
|
||||
|
||||
const mockReplace = jest.fn();
|
||||
jest.mock('utils/browser_history', () => ({
|
||||
getHistory: () => ({replace: mockReplace}),
|
||||
}));
|
||||
|
||||
jest.useFakeTimers({legacyFakeTimers: true});
|
||||
|
||||
describe('components/channel_layout/CenterChannel', () => {
|
||||
describe('components/channel_layout/ChannelIdentifierRouter', () => {
|
||||
const baseProps = {
|
||||
|
||||
match: {
|
||||
isExact: false,
|
||||
params: {
|
||||
@@ -24,20 +33,23 @@ describe('components/channel_layout/CenterChannel', () => {
|
||||
path: '/team/channel/identifier',
|
||||
url: '/team/channel/identifier',
|
||||
},
|
||||
|
||||
actions: {
|
||||
onChannelByIdentifierEnter: jest.fn(),
|
||||
},
|
||||
history: [] as unknown as History,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should call onChannelByIdentifierEnter on props change', () => {
|
||||
const wrapper = shallow(<ChannelIdentifierRouter {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
const {rerender} = render(<ChannelIdentifierRouter {...baseProps}/>);
|
||||
expect(baseProps.actions.onChannelByIdentifierEnter).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.onChannelByIdentifierEnter).toHaveBeenLastCalledWith(baseProps);
|
||||
|
||||
const props2 = {
|
||||
...baseProps,
|
||||
match: {
|
||||
isExact: false,
|
||||
params: {
|
||||
@@ -49,18 +61,11 @@ describe('components/channel_layout/CenterChannel', () => {
|
||||
url: '/team2/channel/identifier2',
|
||||
},
|
||||
};
|
||||
wrapper.setProps(props2);
|
||||
rerender(<ChannelIdentifierRouter {...props2}/>);
|
||||
|
||||
// expect(propsTest.match).toEqual(props2.match);
|
||||
|
||||
//Should clear the timeout if url is changed
|
||||
expect(clearTimeout).toHaveBeenCalledWith((instance as any).replaceUrlTimeout);
|
||||
expect(clearTimeout).toHaveBeenCalled();
|
||||
expect(baseProps.actions.onChannelByIdentifierEnter).toHaveBeenCalledTimes(2);
|
||||
expect(baseProps.actions.onChannelByIdentifierEnter).toHaveBeenLastCalledWith({
|
||||
...baseProps,
|
||||
match: props2.match,
|
||||
actions: baseProps.actions,
|
||||
});
|
||||
expect(baseProps.actions.onChannelByIdentifierEnter).toHaveBeenLastCalledWith(props2);
|
||||
});
|
||||
|
||||
test('should call browserHistory.replace if it is permalink after timer', () => {
|
||||
@@ -78,7 +83,7 @@ describe('components/channel_layout/CenterChannel', () => {
|
||||
url: '/team/channel/identifier/abcd',
|
||||
},
|
||||
};
|
||||
shallow(<ChannelIdentifierRouter {...props}/>);
|
||||
render(<ChannelIdentifierRouter {...props}/>);
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(getHistory().replace).toHaveBeenLastCalledWith('/team/channel/identifier');
|
||||
});
|
||||
@@ -99,8 +104,8 @@ describe('components/channel_layout/CenterChannel', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(<ChannelIdentifierRouter {...baseProps}/>);
|
||||
wrapper.setProps(props);
|
||||
const {rerender} = render(<ChannelIdentifierRouter {...baseProps}/>);
|
||||
rerender(<ChannelIdentifierRouter {...props}/>);
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(getHistory().replace).toHaveBeenLastCalledWith('/team1/channel/identifier1');
|
||||
|
||||
+88
-90
@@ -1,120 +1,118 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/channel_view Should match snapshot if channel is archived 1`] = `
|
||||
<div
|
||||
className="app__content"
|
||||
id="app-content"
|
||||
>
|
||||
<FileUploadOverlay
|
||||
id="centerChannelFileDropOverlay"
|
||||
overlayType="center"
|
||||
/>
|
||||
<ChannelHeader />
|
||||
<ChannelBanner
|
||||
channelId="channelId"
|
||||
/>
|
||||
<DeferredRenderWrapper
|
||||
channelId="channelId"
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
className="post-create__container"
|
||||
id="post-create"
|
||||
class="app__content"
|
||||
id="app-content"
|
||||
>
|
||||
<div
|
||||
className="channel-archived__message"
|
||||
id="channelArchivedMessage"
|
||||
data-testid="FileUploadOverlay"
|
||||
/>
|
||||
<div
|
||||
data-testid="ChannelHeader"
|
||||
/>
|
||||
<div
|
||||
data-testid="ChannelBanner"
|
||||
/>
|
||||
<div
|
||||
data-channel-id="channelId"
|
||||
data-testid="deferred-post-view"
|
||||
/>
|
||||
<div
|
||||
class="post-create__container"
|
||||
id="post-create"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="You are viewing an <b>archived channel</b>. New messages cannot be posted."
|
||||
id="channelView.archivedChannel"
|
||||
values={
|
||||
Object {
|
||||
"b": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-primary channel-archived__close-btn"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
class="channel-archived__message"
|
||||
id="channelArchivedMessage"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Close Channel"
|
||||
id="center_panel.archived.closeChannel"
|
||||
/>
|
||||
</button>
|
||||
You are viewing an
|
||||
<b>
|
||||
archived channel
|
||||
</b>
|
||||
. New messages cannot be posted.
|
||||
<button
|
||||
class="btn btn-primary channel-archived__close-btn"
|
||||
>
|
||||
Close Channel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/channel_view Should match snapshot if channel is deactivated 1`] = `
|
||||
<div
|
||||
className="app__content"
|
||||
id="app-content"
|
||||
>
|
||||
<FileUploadOverlay
|
||||
id="centerChannelFileDropOverlay"
|
||||
overlayType="center"
|
||||
/>
|
||||
<ChannelHeader />
|
||||
<ChannelBanner
|
||||
channelId="channelId"
|
||||
/>
|
||||
<DeferredRenderWrapper
|
||||
channelId="channelId"
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
className="post-create__container AdvancedTextEditor__ctr"
|
||||
id="post-create"
|
||||
class="app__content"
|
||||
id="app-content"
|
||||
>
|
||||
<div
|
||||
className="channel-archived__message"
|
||||
data-testid="FileUploadOverlay"
|
||||
/>
|
||||
<div
|
||||
data-testid="ChannelHeader"
|
||||
/>
|
||||
<div
|
||||
data-testid="ChannelBanner"
|
||||
/>
|
||||
<div
|
||||
data-channel-id="channelId"
|
||||
data-testid="deferred-post-view"
|
||||
/>
|
||||
<div
|
||||
class="post-create__container AdvancedTextEditor__ctr"
|
||||
id="post-create"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="You are viewing an archived channel with a <b>deactivated user</b>. New messages cannot be posted."
|
||||
id="channelView.archivedChannelWithDeactivatedUser"
|
||||
values={
|
||||
Object {
|
||||
"b": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-primary channel-archived__close-btn"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
class="channel-archived__message"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Close Channel"
|
||||
id="center_panel.archived.closeChannel"
|
||||
/>
|
||||
</button>
|
||||
You are viewing an archived channel with a
|
||||
<b>
|
||||
deactivated user
|
||||
</b>
|
||||
. New messages cannot be posted.
|
||||
<button
|
||||
class="btn btn-primary channel-archived__close-btn"
|
||||
>
|
||||
Close Channel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/channel_view Should match snapshot with base props 1`] = `
|
||||
<div
|
||||
className="app__content"
|
||||
id="app-content"
|
||||
>
|
||||
<FileUploadOverlay
|
||||
id="centerChannelFileDropOverlay"
|
||||
overlayType="center"
|
||||
/>
|
||||
<ChannelHeader />
|
||||
<ChannelBanner
|
||||
channelId="channelId"
|
||||
/>
|
||||
<DeferredRenderWrapper
|
||||
channelId="channelId"
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
className="post-create__container AdvancedTextEditor__ctr"
|
||||
data-testid="post-create"
|
||||
id="post-create"
|
||||
class="app__content"
|
||||
id="app-content"
|
||||
>
|
||||
<AdvancedCreatePost />
|
||||
<div
|
||||
data-testid="FileUploadOverlay"
|
||||
/>
|
||||
<div
|
||||
data-testid="ChannelHeader"
|
||||
/>
|
||||
<div
|
||||
data-testid="ChannelBanner"
|
||||
/>
|
||||
<div
|
||||
data-channel-id="channelId"
|
||||
data-testid="deferred-post-view"
|
||||
/>
|
||||
<div
|
||||
class="post-create__container AdvancedTextEditor__ctr"
|
||||
data-testid="post-create"
|
||||
id="post-create"
|
||||
>
|
||||
<div
|
||||
data-testid="AdvancedCreatePost"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,12 +1,44 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
|
||||
import ChannelView from './channel_view';
|
||||
import type {Props} from './channel_view';
|
||||
|
||||
jest.mock('components/async_load', () => ({
|
||||
makeAsyncComponent: (name: string) => {
|
||||
const Component = () => <div data-testid={name}/>;
|
||||
Component.displayName = name;
|
||||
return Component;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('components/deferComponentRender', () => {
|
||||
return jest.fn(() => {
|
||||
return function DeferredPostView(props: any) {
|
||||
return (
|
||||
<div
|
||||
data-testid='deferred-post-view'
|
||||
{...(props.focusedPostId ? {'data-focused-post-id': props.focusedPostId} : {})}
|
||||
data-channel-id={props.channelId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
jest.mock('components/post_view', () => () => <div data-testid='post-view'/>);
|
||||
|
||||
jest.mock('client/web_websocket_client', () => ({
|
||||
__esModule: true,
|
||||
default: {updateActiveChannel: jest.fn()},
|
||||
}));
|
||||
|
||||
jest.mock('./input_loading', () => () => <div data-testid='input-loading'/>);
|
||||
|
||||
describe('components/channel_view', () => {
|
||||
const baseProps: Props = {
|
||||
channelId: 'channelId',
|
||||
@@ -32,50 +64,73 @@ describe('components/channel_view', () => {
|
||||
};
|
||||
|
||||
it('Should match snapshot with base props', () => {
|
||||
const wrapper = shallow(<ChannelView {...baseProps}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<ChannelView {...baseProps}/>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Should match snapshot if channel is archived', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<ChannelView
|
||||
{...baseProps}
|
||||
channelIsArchived={true}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Should match snapshot if channel is deactivated', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<ChannelView
|
||||
{...baseProps}
|
||||
deactivatedChannel={true}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Should have focusedPostId state based on props', () => {
|
||||
const wrapper = shallow(<ChannelView {...baseProps}/>);
|
||||
expect(wrapper.state('focusedPostId')).toEqual(undefined);
|
||||
const {rerender} = renderWithContext(<ChannelView {...baseProps}/>);
|
||||
|
||||
wrapper.setProps({channelId: 'newChannelId', match: {url: '/team/channel/channelId/postId', params: {postid: 'postid'}}});
|
||||
expect(wrapper.state('focusedPostId')).toEqual('postid');
|
||||
wrapper.setProps({channelId: 'newChannelId', match: {url: '/team/channel/channelId/postId1', params: {postid: 'postid1'}}});
|
||||
expect(wrapper.state('focusedPostId')).toEqual('postid1');
|
||||
// Initially no focusedPostId
|
||||
expect(screen.getByTestId('deferred-post-view')).not.toHaveAttribute('data-focused-post-id');
|
||||
|
||||
// Rerender with postid
|
||||
rerender(
|
||||
<ChannelView
|
||||
{...baseProps}
|
||||
channelId='newChannelId'
|
||||
match={{url: '/team/channel/channelId/postId', params: {postid: 'postid'}} as Props['match']}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId('deferred-post-view')).toHaveAttribute('data-focused-post-id', 'postid');
|
||||
|
||||
// Rerender with different postid
|
||||
rerender(
|
||||
<ChannelView
|
||||
{...baseProps}
|
||||
channelId='newChannelId'
|
||||
match={{url: '/team/channel/channelId/postId1', params: {postid: 'postid1'}} as Props['match']}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId('deferred-post-view')).toHaveAttribute('data-focused-post-id', 'postid1');
|
||||
});
|
||||
|
||||
it('should call fetchRecentMentions on componentDidUpdate', () => {
|
||||
const wrapper = shallow(
|
||||
const {rerender} = renderWithContext(
|
||||
<ChannelView
|
||||
{...baseProps}
|
||||
canRestrictDirectMessage={true}
|
||||
restrictDirectMessage={undefined as any}
|
||||
/>,
|
||||
);
|
||||
const instance = wrapper.instance() as ChannelView;
|
||||
wrapper.setProps({channelId: 'newChannelId'});
|
||||
expect(instance.props.fetchIsRestrictedDM).toHaveBeenCalledTimes(1);
|
||||
rerender(
|
||||
<ChannelView
|
||||
{...baseProps}
|
||||
canRestrictDirectMessage={true}
|
||||
restrictDirectMessage={undefined as any}
|
||||
channelId='newChannelId'
|
||||
/>,
|
||||
);
|
||||
expect(baseProps.fetchIsRestrictedDM).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
+3
-13
@@ -1,17 +1,7 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`/components/common/hocs/cloud/with_use_get_usage_deltas should pass the useGetUsageDeltas 1`] = `
|
||||
<Component>
|
||||
<mockConstructor
|
||||
usageDeltas={
|
||||
Object {
|
||||
"teams": Object {
|
||||
"active": -1,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<div />
|
||||
</mockConstructor>
|
||||
</Component>
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
`;
|
||||
|
||||
+5
-4
@@ -1,10 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {mount} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {ComponentType} from 'react';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import withGetCloudSubscription from './with_get_cloud_subscription';
|
||||
|
||||
describe('/components/common/hocs/with_get_cloud_subcription', () => {
|
||||
@@ -22,7 +23,7 @@ describe('/components/common/hocs/with_get_cloud_subcription', () => {
|
||||
|
||||
const getCloudSubscriptionSpy = jest.spyOn(actions, 'getCloudSubscription');
|
||||
|
||||
mount(
|
||||
renderWithContext(
|
||||
<EnhancedComponent
|
||||
isCloud={true}
|
||||
actions={actions}
|
||||
@@ -42,7 +43,7 @@ describe('/components/common/hocs/with_get_cloud_subcription', () => {
|
||||
|
||||
const getCloudSubscriptionSpy = jest.spyOn(actions, 'getCloudSubscription');
|
||||
|
||||
mount(
|
||||
renderWithContext(
|
||||
<EnhancedComponent
|
||||
isCloud={false}
|
||||
actions={actions}
|
||||
@@ -62,7 +63,7 @@ describe('/components/common/hocs/with_get_cloud_subcription', () => {
|
||||
|
||||
const getCloudSubscriptionSpy = jest.spyOn(actions, 'getCloudSubscription');
|
||||
|
||||
mount(
|
||||
renderWithContext(
|
||||
<EnhancedComponent
|
||||
isCloud={true}
|
||||
actions={actions}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {mount} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {ComponentType} from 'react';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import withUseGetUsageDeltas from './with_use_get_usage_deltas';
|
||||
|
||||
jest.mock('components/common/hooks/useGetUsageDeltas', () => jest.fn(() => ({
|
||||
@@ -19,10 +20,10 @@ describe('/components/common/hocs/cloud/with_use_get_usage_deltas', () => {
|
||||
test('should pass the useGetUsageDeltas', () => {
|
||||
const WrappedComponent = withUseGetUsageDeltas(TestComponent);
|
||||
|
||||
const wrapper = mount(
|
||||
const {container} = renderWithContext(
|
||||
<WrappedComponent/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import type {ChannelType} from '@mattermost/types/channels';
|
||||
|
||||
import {loadProfilesForSidebar} from 'actions/user_actions';
|
||||
|
||||
import {renderWithContext, runPostRenderAct} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import DataPrefetch from './data_prefetch';
|
||||
@@ -71,28 +71,30 @@ describe('/components/data_prefetch', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockQueue.splice(0, mockQueue.length);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should fetch posts for current channel on first channel load', async () => {
|
||||
const props = defaultProps;
|
||||
const wrapper = shallow<DataPrefetch>(
|
||||
const props = {...defaultProps};
|
||||
const {rerender} = renderWithContext(
|
||||
<DataPrefetch {...props}/>,
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.prefetchPosts = jest.fn();
|
||||
|
||||
// Change channels and wait for async componentDidUpdate to resolve
|
||||
wrapper.setProps({currentChannelId: 'currentChannelId'});
|
||||
await Promise.resolve(true);
|
||||
rerender(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
currentChannelId='currentChannelId'
|
||||
/>,
|
||||
);
|
||||
await runPostRenderAct();
|
||||
|
||||
expect(mockQueue).toHaveLength(1);
|
||||
expect(instance.prefetchPosts).not.toHaveBeenCalled();
|
||||
|
||||
// Manually run queued tasks
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(1);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('currentChannelId');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(1);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('currentChannelId', undefined);
|
||||
});
|
||||
|
||||
test('should fetch profiles for sidebar on sidebar load', async () => {
|
||||
@@ -100,20 +102,30 @@ describe('/components/data_prefetch', () => {
|
||||
...defaultProps,
|
||||
sidebarLoaded: false,
|
||||
};
|
||||
const wrapper = shallow<DataPrefetch>(
|
||||
const {rerender} = renderWithContext(
|
||||
<DataPrefetch {...props}/>,
|
||||
);
|
||||
|
||||
expect(loadProfilesForSidebar).not.toHaveBeenCalled();
|
||||
|
||||
// Finish loading the sidebar
|
||||
wrapper.setProps({sidebarLoaded: true});
|
||||
await Promise.resolve(true);
|
||||
rerender(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
sidebarLoaded={true}
|
||||
/>,
|
||||
);
|
||||
await runPostRenderAct();
|
||||
|
||||
expect(loadProfilesForSidebar).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Reload the sidebar
|
||||
wrapper.setProps({sidebarLoaded: true});
|
||||
// Reload the sidebar - should not call again
|
||||
rerender(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
sidebarLoaded={true}
|
||||
/>,
|
||||
);
|
||||
await Promise.resolve(true);
|
||||
|
||||
expect(loadProfilesForSidebar).toHaveBeenCalledTimes(1);
|
||||
@@ -128,38 +140,39 @@ describe('/components/data_prefetch', () => {
|
||||
3: [],
|
||||
},
|
||||
};
|
||||
const wrapper = shallow<DataPrefetch>(
|
||||
const {rerender} = renderWithContext(
|
||||
<DataPrefetch {...props}/>,
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.prefetchPosts = jest.fn();
|
||||
|
||||
wrapper.setProps({currentChannelId: 'currentChannelId'});
|
||||
await Promise.resolve(true);
|
||||
rerender(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
currentChannelId='currentChannelId'
|
||||
/>,
|
||||
);
|
||||
await runPostRenderAct();
|
||||
|
||||
expect(mockQueue).toHaveLength(5); // current channel, mentioned channels, unread channels
|
||||
expect(instance.prefetchPosts).not.toHaveBeenCalled();
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(1);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('currentChannelId');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(1);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('currentChannelId', undefined);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(2);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('mentionChannel0');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(2);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('mentionChannel0', undefined);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(3);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('mentionChannel1');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(3);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('mentionChannel1', undefined);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(4);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('unreadChannel0');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(4);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('unreadChannel0', undefined);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(5);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('unreadChannel1');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(5);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('unreadChannel1', undefined);
|
||||
});
|
||||
|
||||
test('should cancel fetch and requeue channels when prefetch queue changes', async () => {
|
||||
@@ -170,33 +183,40 @@ describe('/components/data_prefetch', () => {
|
||||
2: ['unreadChannel0', 'unreadChannel1', 'unreadChannel2'],
|
||||
},
|
||||
};
|
||||
const wrapper = shallow<DataPrefetch>(
|
||||
const {rerender} = renderWithContext(
|
||||
<DataPrefetch {...props}/>,
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.prefetchPosts = jest.fn();
|
||||
|
||||
wrapper.setProps({currentChannelId: 'currentChannelId'});
|
||||
await Promise.resolve(true);
|
||||
rerender(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
currentChannelId='currentChannelId'
|
||||
/>,
|
||||
);
|
||||
await runPostRenderAct();
|
||||
|
||||
expect(mockQueue).toHaveLength(4);
|
||||
expect(instance.prefetchPosts).not.toHaveBeenCalled();
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(1);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('currentChannelId');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(1);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('currentChannelId', undefined);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(2);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('unreadChannel0');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(2);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('unreadChannel0', undefined);
|
||||
|
||||
wrapper.setProps({
|
||||
prefetchQueueObj: {
|
||||
1: ['mentionChannel0', 'mentionChannel1'],
|
||||
2: ['unreadChannel2', 'unreadChannel3'],
|
||||
},
|
||||
});
|
||||
const newPrefetchQueueObj = {
|
||||
1: ['mentionChannel0', 'mentionChannel1'],
|
||||
2: ['unreadChannel2', 'unreadChannel3'],
|
||||
};
|
||||
|
||||
rerender(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
currentChannelId='currentChannelId'
|
||||
prefetchQueueObj={newPrefetchQueueObj}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Check queue has been cleared and wait for async componentDidUpdate to complete
|
||||
expect(mockQueue).toHaveLength(0);
|
||||
@@ -205,20 +225,20 @@ describe('/components/data_prefetch', () => {
|
||||
expect(mockQueue).toHaveLength(4);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(3);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('mentionChannel0');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(3);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('mentionChannel0', undefined);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(4);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('mentionChannel1');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(4);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('mentionChannel1', undefined);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(5);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('unreadChannel2');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(5);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('unreadChannel2', undefined);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(6);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('unreadChannel3');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(6);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('unreadChannel3', undefined);
|
||||
});
|
||||
|
||||
test('should skip making request for posts if a request was made', async () => {
|
||||
@@ -232,25 +252,27 @@ describe('/components/data_prefetch', () => {
|
||||
unreadChannel: 'success',
|
||||
},
|
||||
};
|
||||
const wrapper = shallow<DataPrefetch>(
|
||||
const {rerender} = renderWithContext(
|
||||
<DataPrefetch {...props}/>,
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.prefetchPosts = jest.fn();
|
||||
|
||||
wrapper.setProps({currentChannelId: 'currentChannelId'});
|
||||
await Promise.resolve(true);
|
||||
rerender(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
currentChannelId='currentChannelId'
|
||||
/>,
|
||||
);
|
||||
await runPostRenderAct();
|
||||
|
||||
expect(mockQueue).toHaveLength(2);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(1);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('currentChannelId');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(1);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('currentChannelId', undefined);
|
||||
|
||||
mockQueue.shift()!();
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledTimes(2);
|
||||
expect(instance.prefetchPosts).toHaveBeenCalledWith('mentionChannel');
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledTimes(2);
|
||||
expect(props.actions.prefetchChannelPosts).toHaveBeenCalledWith('mentionChannel', undefined);
|
||||
});
|
||||
|
||||
test('should add delay if last post is made in last min', async () => {
|
||||
@@ -280,12 +302,17 @@ describe('/components/data_prefetch', () => {
|
||||
last_root_post_at: 12345,
|
||||
}],
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const {rerender} = renderWithContext(
|
||||
<DataPrefetch {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.setProps({currentChannelId: 'currentChannelId'});
|
||||
await Promise.resolve(true);
|
||||
rerender(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
currentChannelId='currentChannelId'
|
||||
/>,
|
||||
);
|
||||
await runPostRenderAct();
|
||||
|
||||
expect(mockQueue).toHaveLength(2);
|
||||
|
||||
@@ -327,12 +354,17 @@ describe('/components/data_prefetch', () => {
|
||||
last_root_post_at: 12345,
|
||||
}],
|
||||
};
|
||||
const wrapper = shallow<DataPrefetch>(
|
||||
const {rerender} = renderWithContext(
|
||||
<DataPrefetch {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.setProps({currentChannelId: 'currentChannelId'});
|
||||
await Promise.resolve(true);
|
||||
rerender(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
currentChannelId='currentChannelId'
|
||||
/>,
|
||||
);
|
||||
await runPostRenderAct();
|
||||
|
||||
expect(mockQueue).toHaveLength(2);
|
||||
|
||||
@@ -354,26 +386,37 @@ describe('/components/data_prefetch', () => {
|
||||
sidebarLoaded: false,
|
||||
};
|
||||
|
||||
let wrapper = shallow<DataPrefetch>(
|
||||
const {rerender, unmount} = renderWithContext(
|
||||
<DataPrefetch {...props}/>,
|
||||
);
|
||||
wrapper.setProps({});
|
||||
|
||||
// Rerender with same props - no sidebar loaded
|
||||
rerender(<DataPrefetch {...props}/>);
|
||||
|
||||
expect(loadProfilesForSidebar).not.toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
|
||||
// With current channel loaded first
|
||||
wrapper = shallow<DataPrefetch>(
|
||||
const {rerender: rerender2} = renderWithContext(
|
||||
<DataPrefetch {...props}/>,
|
||||
);
|
||||
wrapper.setProps({
|
||||
currentChannelId: 'channel',
|
||||
});
|
||||
rerender2(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
currentChannelId='channel'
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(loadProfilesForSidebar).not.toHaveBeenCalled();
|
||||
|
||||
wrapper.setProps({
|
||||
sidebarLoaded: true,
|
||||
});
|
||||
rerender2(
|
||||
<DataPrefetch
|
||||
{...props}
|
||||
currentChannelId='channel'
|
||||
sidebarLoaded={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(loadProfilesForSidebar).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Modal} from 'react-bootstrap';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import type {DialogElement as TDialogElement} from '@mattermost/types/integrations';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {renderWithContext, screen, userEvent, waitFor} from 'tests/react_testing_utils';
|
||||
import EmojiMap from 'utils/emoji_map';
|
||||
|
||||
import type {Props} from './interactive_dialog';
|
||||
import InteractiveDialog from './interactive_dialog';
|
||||
|
||||
const submitEvent = {
|
||||
preventDefault: jest.fn(),
|
||||
} as unknown as React.FormEvent<HTMLFormElement>;
|
||||
|
||||
describe('components/interactive_dialog/InteractiveDialog', () => {
|
||||
const baseProps: Props = {
|
||||
url: 'http://example.com',
|
||||
@@ -47,23 +39,33 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
|
||||
lookupInteractiveDialog: jest.fn(),
|
||||
},
|
||||
};
|
||||
const wrapper = shallow<InteractiveDialog>(<InteractiveDialog {...props}/>);
|
||||
renderWithContext(<InteractiveDialog {...props}/>);
|
||||
|
||||
await wrapper.instance().handleSubmit(submitEvent);
|
||||
const submitButton = screen.getByText('Yes');
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
const expected = (
|
||||
<div className='error-text'>
|
||||
{'This is an error.'}
|
||||
</div>
|
||||
);
|
||||
expect(wrapper.find(Modal.Footer).containsMatchingElement(expected)).toBe(true);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('This is an error.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const errorElement = screen.getByText('This is an error.');
|
||||
expect(errorElement).toHaveClass('error-text');
|
||||
});
|
||||
|
||||
test('should not appear when submit does not return an error', async () => {
|
||||
const wrapper = shallow<InteractiveDialog>(<InteractiveDialog {...baseProps}/>);
|
||||
await wrapper.instance().handleSubmit(submitEvent);
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {
|
||||
submitInteractiveDialog: jest.fn().mockResolvedValue({}),
|
||||
lookupInteractiveDialog: jest.fn(),
|
||||
},
|
||||
};
|
||||
renderWithContext(<InteractiveDialog {...props}/>);
|
||||
|
||||
expect(wrapper.find(Modal.Footer).exists('.error-text')).toBe(false);
|
||||
const submitButton = screen.getByText('Yes');
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
expect(document.querySelector('.error-text')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,20 +90,14 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
|
||||
max_length: 0,
|
||||
};
|
||||
|
||||
const {elements, ...rest} = baseProps;
|
||||
elements?.push(selectElement);
|
||||
const props = {
|
||||
...rest,
|
||||
elements,
|
||||
...baseProps,
|
||||
elements: [selectElement],
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<InteractiveDialog {...props}/>
|
||||
</Provider>,
|
||||
);
|
||||
expect(wrapper.find(Modal.Body).find('input').find({defaultValue: 'Option3'}).exists()).toBe(true);
|
||||
renderWithContext(<InteractiveDialog {...props}/>);
|
||||
|
||||
expect(screen.getByDisplayValue('Option3')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -120,14 +116,6 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
|
||||
max_length: 0,
|
||||
options: [],
|
||||
};
|
||||
const {elements, ...rest} = baseProps;
|
||||
const props = {
|
||||
...rest,
|
||||
elements: [
|
||||
...elements || [],
|
||||
element,
|
||||
],
|
||||
};
|
||||
|
||||
const testCases = [
|
||||
{description: 'no default', expectedChecked: false},
|
||||
@@ -140,19 +128,26 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => test(`should interpret ${testCase.description}`, () => {
|
||||
const testElement = {...element};
|
||||
if (testCase.default === undefined) {
|
||||
delete (element as any).default;
|
||||
delete (testElement as any).default;
|
||||
} else {
|
||||
(element as any).default = testCase.default;
|
||||
(testElement as any).default = testCase.default;
|
||||
}
|
||||
|
||||
const store = mockStore({});
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<InteractiveDialog {...props}/>
|
||||
</Provider>,
|
||||
);
|
||||
expect(wrapper.find(Modal.Body).find('input').find({checked: testCase.expectedChecked}).exists()).toBe(true);
|
||||
const props = {
|
||||
...baseProps,
|
||||
elements: [testElement],
|
||||
};
|
||||
|
||||
renderWithContext(<InteractiveDialog {...props}/>);
|
||||
|
||||
const checkbox = screen.getByRole('checkbox');
|
||||
if (testCase.expectedChecked) {
|
||||
expect(checkbox).toBeChecked();
|
||||
} else {
|
||||
expect(checkbox).not.toBeChecked();
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import deepFreeze from 'mattermost-redux/utils/deep_freeze';
|
||||
|
||||
import AlertIcon from 'components/widgets/icons/alert_icon';
|
||||
import EmailIcon from 'components/widgets/icons/mail_icon';
|
||||
import BotTag from 'components/widgets/tag/bot_tag';
|
||||
import GuestTag from 'components/widgets/tag/guest_tag';
|
||||
import Avatar from 'components/widgets/users/avatar';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
|
||||
import ResultTable from './result_table';
|
||||
import type {Props} from './result_table';
|
||||
@@ -75,8 +68,8 @@ describe('ResultTable', () => {
|
||||
email: 'aa@aa.aa',
|
||||
reason: {id: 'some_reason', defaultMessage: 'some reason'},
|
||||
}];
|
||||
const wrapper = shallow(<ResultTable {...props}/>);
|
||||
expect(wrapper.find(EmailIcon).length).toBe(1);
|
||||
const {container} = renderWithContext(<ResultTable {...props}/>);
|
||||
expect(container.querySelector('.mail-icon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('unsent invites render as unsent invites', () => {
|
||||
@@ -84,8 +77,8 @@ describe('ResultTable', () => {
|
||||
text: '@incomplete_userna',
|
||||
reason: {id: 'incomplete_user', defaultMessage: 'This was not a complete user'},
|
||||
}];
|
||||
const wrapper = shallow(<ResultTable {...props}/>);
|
||||
expect(wrapper.find(AlertIcon).length).toBe(1);
|
||||
const {container} = renderWithContext(<ResultTable {...props}/>);
|
||||
expect(container.querySelector('.alert-icon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('user invites render as users', () => {
|
||||
@@ -93,10 +86,10 @@ describe('ResultTable', () => {
|
||||
user: defaultUser,
|
||||
reason: {id: 'success', defaultMessage: 'added successfully'},
|
||||
}];
|
||||
const wrapper = shallow(<ResultTable {...props}/>);
|
||||
expect(wrapper.find(Avatar).length).toBe(1);
|
||||
expect(wrapper.find(BotTag).length).toBe(0);
|
||||
expect(wrapper.find(GuestTag).length).toBe(0);
|
||||
const {container} = renderWithContext(<ResultTable {...props}/>);
|
||||
expect(container.querySelector('.Avatar')).toBeInTheDocument();
|
||||
expect(screen.queryByText('BOT')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('GUEST')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('bots render as bots', () => {
|
||||
@@ -107,10 +100,10 @@ describe('ResultTable', () => {
|
||||
},
|
||||
reason: {id: 'success', defaultMessage: 'added successfully'},
|
||||
}];
|
||||
const wrapper = shallow(<ResultTable {...props}/>);
|
||||
expect(wrapper.find(Avatar).length).toBe(1);
|
||||
expect(wrapper.find(BotTag).length).toBe(1);
|
||||
expect(wrapper.find(GuestTag).length).toBe(0);
|
||||
const {container} = renderWithContext(<ResultTable {...props}/>);
|
||||
expect(container.querySelector('.Avatar')).toBeInTheDocument();
|
||||
expect(screen.getByText('BOT')).toBeInTheDocument();
|
||||
expect(screen.queryByText('GUEST')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('guests render as guests', () => {
|
||||
@@ -121,21 +114,21 @@ describe('ResultTable', () => {
|
||||
},
|
||||
reason: {id: 'success', defaultMessage: 'added successfully'},
|
||||
}];
|
||||
const wrapper = shallow(<ResultTable {...props}/>);
|
||||
expect(wrapper.find(Avatar).length).toBe(1);
|
||||
expect(wrapper.find(BotTag).length).toBe(0);
|
||||
expect(wrapper.find(GuestTag).length).toBe(1);
|
||||
const {container} = renderWithContext(<ResultTable {...props}/>);
|
||||
expect(container.querySelector('.Avatar')).toBeInTheDocument();
|
||||
expect(screen.queryByText('BOT')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('GUEST')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders success banner when invites were sent', () => {
|
||||
props.sent = true;
|
||||
const wrapper = mountWithIntl(<ResultTable {...props}/>);
|
||||
expect(wrapper.find('h2').at(0).text()).toContain('Successful Invites');
|
||||
renderWithContext(<ResultTable {...props}/>);
|
||||
expect(screen.getByText('Successful Invites')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders not sent banner when invites were not sent', () => {
|
||||
props.sent = false;
|
||||
const wrapper = mountWithIntl(<ResultTable {...props}/>);
|
||||
expect(wrapper.find('h2').at(0).text()).toContain('Invitations Not Sent');
|
||||
renderWithContext(<ResultTable {...props}/>);
|
||||
expect(screen.getByText('Invitations Not Sent')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import React from 'react';
|
||||
import LatexInline from 'components/latex_inline/latex_inline';
|
||||
|
||||
import {withIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {act, renderWithContext} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, waitFor} from 'tests/react_testing_utils';
|
||||
|
||||
describe('components/LatexInline', () => {
|
||||
const defaultProps = {
|
||||
@@ -15,27 +15,20 @@ describe('components/LatexInline', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', async () => {
|
||||
let container;
|
||||
|
||||
await act(async () => {
|
||||
const result = renderWithContext(withIntl(<LatexInline {...defaultProps}/>));
|
||||
container = result.container;
|
||||
const {container} = renderWithContext(withIntl(<LatexInline {...defaultProps}/>));
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[data-testid="latex-enabled"]')).toBeInTheDocument();
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('latex is disabled', async () => {
|
||||
test('latex is disabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
enableInlineLatex: false,
|
||||
};
|
||||
|
||||
let container;
|
||||
|
||||
await act(async () => {
|
||||
const result = renderWithContext(withIntl(<LatexInline {...props}/>));
|
||||
container = result.container;
|
||||
});
|
||||
const {container} = renderWithContext(withIntl(<LatexInline {...props}/>));
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -45,11 +38,9 @@ describe('components/LatexInline', () => {
|
||||
enableInlineLatex: true,
|
||||
};
|
||||
|
||||
let container;
|
||||
|
||||
await act(async () => {
|
||||
const result = renderWithContext(withIntl(<LatexInline {...props}/>));
|
||||
container = result.container;
|
||||
const {container} = renderWithContext(withIntl(<LatexInline {...props}/>));
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[data-testid="latex-enabled"]')).toBeInTheDocument();
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
+29
-27
@@ -1,39 +1,41 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/MarkdownImageExpand should match snapshot for collapsed embeds 1`] = `
|
||||
<div
|
||||
className="markdown-image-expand "
|
||||
>
|
||||
<button
|
||||
className="markdown-image-expand__expand-button"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
<div>
|
||||
<div
|
||||
class="markdown-image-expand "
|
||||
>
|
||||
<span
|
||||
className="icon icon-menu-right markdown-image-expand__expand-icon"
|
||||
/>
|
||||
<span
|
||||
className="markdown-image-expand__alt-text"
|
||||
<button
|
||||
class="markdown-image-expand__expand-button"
|
||||
type="button"
|
||||
>
|
||||
Some alt text
|
||||
</span>
|
||||
</button>
|
||||
<span
|
||||
class="icon icon-menu-right markdown-image-expand__expand-icon"
|
||||
/>
|
||||
<span
|
||||
class="markdown-image-expand__alt-text"
|
||||
>
|
||||
Some alt text
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/MarkdownImageExpand should match snapshot for expanded embeds 1`] = `
|
||||
<div
|
||||
className="markdown-image-expand markdown-image-expand--expanded"
|
||||
>
|
||||
<button
|
||||
className="markdown-image-expand__collapse-button"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
<div>
|
||||
<div
|
||||
class="markdown-image-expand markdown-image-expand--expanded"
|
||||
>
|
||||
<span
|
||||
className="icon icon-menu-down"
|
||||
/>
|
||||
</button>
|
||||
An image to expand
|
||||
<button
|
||||
class="markdown-image-expand__collapse-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="icon icon-menu-down"
|
||||
/>
|
||||
</button>
|
||||
An image to expand
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
+14
-11
@@ -1,16 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithContext, userEvent} from 'tests/react_testing_utils';
|
||||
|
||||
import MarkdownImageExpand from './markdown_image_expand';
|
||||
|
||||
describe('components/MarkdownImageExpand', () => {
|
||||
it('should match snapshot for collapsed embeds', () => {
|
||||
const toggleHandler = jest.fn();
|
||||
const imageCollapseHandler = jest.fn();
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<MarkdownImageExpand
|
||||
alt={'Some alt text'}
|
||||
postId={'abc'}
|
||||
@@ -21,13 +22,13 @@ describe('components/MarkdownImageExpand', () => {
|
||||
>{'An image to expand'}</MarkdownImageExpand>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot for expanded embeds', () => {
|
||||
const toggleHandler = jest.fn();
|
||||
const imageCollapseHandler = jest.fn();
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<MarkdownImageExpand
|
||||
alt={'Some alt text'}
|
||||
postId={'abc'}
|
||||
@@ -38,13 +39,13 @@ describe('components/MarkdownImageExpand', () => {
|
||||
>{'An image to expand'}</MarkdownImageExpand>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should emit toggle action on collapse button click', () => {
|
||||
it('should emit toggle action on collapse button click', async () => {
|
||||
const toggleHandler = jest.fn();
|
||||
const imageCollapseHandler = jest.fn();
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<MarkdownImageExpand
|
||||
alt={'Some alt text'}
|
||||
postId={'abc'}
|
||||
@@ -55,15 +56,16 @@ describe('components/MarkdownImageExpand', () => {
|
||||
>{'An image to expand'}</MarkdownImageExpand>,
|
||||
);
|
||||
|
||||
wrapper.find('.markdown-image-expand__collapse-button').simulate('click');
|
||||
const collapseButton = container.querySelector('.markdown-image-expand__collapse-button')!;
|
||||
await userEvent.click(collapseButton);
|
||||
|
||||
expect(imageCollapseHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit toggle action on expand button click', () => {
|
||||
it('should emit toggle action on expand button click', async () => {
|
||||
const toggleHandler = jest.fn();
|
||||
const imageCollapseHandler = jest.fn();
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<MarkdownImageExpand
|
||||
alt={'Some alt text'}
|
||||
postId={'abc'}
|
||||
@@ -74,7 +76,8 @@ describe('components/MarkdownImageExpand', () => {
|
||||
>{'An image to expand'}</MarkdownImageExpand>,
|
||||
);
|
||||
|
||||
wrapper.find('.markdown-image-expand__expand-button').simulate('click');
|
||||
const expandButton = container.querySelector('.markdown-image-expand__expand-button')!;
|
||||
await userEvent.click(expandButton);
|
||||
|
||||
expect(imageCollapseHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
+1
-29
@@ -1,31 +1,3 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/ModalController component should match snapshot without any modals 1`] = `
|
||||
<Provider
|
||||
store={
|
||||
Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ModalController)>
|
||||
<ModalController
|
||||
actions={
|
||||
Object {
|
||||
"closeModal": [Function],
|
||||
}
|
||||
}
|
||||
modals={
|
||||
Object {
|
||||
"modalState": Object {},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Connect(ModalController)>
|
||||
</Provider>
|
||||
`;
|
||||
exports[`components/ModalController component should match snapshot without any modals 1`] = `<div />`;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {mount} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Modal} from 'react-bootstrap';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import {closeModal} from 'actions/views/modals';
|
||||
|
||||
import mockStore from 'tests/test_store';
|
||||
import {renderWithContext, act, userEvent} from 'tests/react_testing_utils';
|
||||
|
||||
import ModalController from '.';
|
||||
|
||||
@@ -59,16 +57,14 @@ describe('components/ModalController', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<ModalController/>
|
||||
</Provider>,
|
||||
const {container} = renderWithContext(
|
||||
<ModalController/>,
|
||||
state,
|
||||
{useMockedStore: true},
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find('ModalController > *').length).toBe(0);
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(container.childElementCount).toBe(0);
|
||||
expect(document.getElementsByClassName('modal-dialog').length).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -87,18 +83,16 @@ describe('components/ModalController', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
|
||||
mount(
|
||||
<Provider store={store}>
|
||||
<ModalController/>
|
||||
</Provider>,
|
||||
renderWithContext(
|
||||
<ModalController/>,
|
||||
state,
|
||||
{useMockedStore: true},
|
||||
);
|
||||
|
||||
expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
||||
});
|
||||
|
||||
test('should pass onExited to modal to allow a modal to remove itself', () => {
|
||||
test('should pass onExited to modal to allow a modal to remove itself', async () => {
|
||||
const state = {
|
||||
views: {
|
||||
modals: {
|
||||
@@ -113,26 +107,30 @@ describe('components/ModalController', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<ModalController/>
|
||||
</Provider>,
|
||||
const {store} = renderWithContext(
|
||||
<ModalController/>,
|
||||
state,
|
||||
{useMockedStore: true},
|
||||
);
|
||||
|
||||
expect(wrapper.find(TestModal).exists()).toBe(true);
|
||||
expect(wrapper.find(TestModal).prop('onExited')).toBeDefined();
|
||||
expect(wrapper.find(Modal).prop('onExited')).toBeDefined();
|
||||
// Verify the modal is rendered
|
||||
expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
||||
|
||||
wrapper.find(TestModal).prop('onExited')!();
|
||||
// Click the close button to trigger modal close flow (onHide -> setState show:false -> onExited)
|
||||
const closeButton = document.querySelector('.close') as HTMLElement;
|
||||
await userEvent.click(closeButton);
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
// Wait for the modal's exit transition to complete and fire onExited
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
});
|
||||
|
||||
expect((store as unknown as {getActions: () => unknown[]}).getActions()).toEqual([
|
||||
closeModal(modalId),
|
||||
]);
|
||||
});
|
||||
|
||||
test('should call a provided onExited in addition to removing the modal', () => {
|
||||
test('should call a provided onExited in addition to removing the modal', async () => {
|
||||
const onExited = jest.fn();
|
||||
|
||||
const state = {
|
||||
@@ -151,21 +149,25 @@ describe('components/ModalController', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<ModalController/>
|
||||
</Provider>,
|
||||
renderWithContext(
|
||||
<ModalController/>,
|
||||
state,
|
||||
{useMockedStore: true},
|
||||
);
|
||||
|
||||
expect(wrapper.find(TestModal).exists()).toBe(true);
|
||||
expect(wrapper.find(TestModal).prop('onExited')).toBeDefined();
|
||||
expect(wrapper.find(Modal).prop('onExited')).toBeDefined();
|
||||
// Verify the modal is rendered
|
||||
expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
||||
|
||||
expect(onExited).not.toHaveBeenCalled();
|
||||
|
||||
wrapper.find(TestModal).prop('onExited')!();
|
||||
// Click the close button to trigger modal close flow (onHide -> setState show:false -> onExited)
|
||||
const closeButton = document.querySelector('.close') as HTMLElement;
|
||||
await userEvent.click(closeButton);
|
||||
|
||||
// Wait for the modal's exit transition to complete and fire onExited
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
});
|
||||
|
||||
expect(onExited).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
+5
-42
@@ -1,49 +1,12 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/PermalinkView should match snapshot 1`] = `
|
||||
<div
|
||||
className="app__content"
|
||||
id="app-content"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/PermalinkView should match snapshot with archived channel 1`] = `
|
||||
<Memo(PermalinkView)
|
||||
actions={
|
||||
Object {
|
||||
"focusPost": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
"post_id",
|
||||
"return_to",
|
||||
"current_user",
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
channelId="channel_id"
|
||||
channelIsArchived={true}
|
||||
currentUserId="current_user"
|
||||
match={
|
||||
Object {
|
||||
"params": Object {
|
||||
"postid": "post_id",
|
||||
},
|
||||
}
|
||||
}
|
||||
returnTo="return_to"
|
||||
teamName="team_name"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="app__content"
|
||||
class="app__content"
|
||||
id="app-content"
|
||||
/>
|
||||
</Memo(PermalinkView)>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/PermalinkView should match snapshot with archived channel 1`] = `<div />`;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import nock from 'nock';
|
||||
import React from 'react';
|
||||
import type {ComponentProps} from 'react';
|
||||
@@ -18,8 +17,7 @@ import {focusPost} from 'components/permalink_view/actions';
|
||||
import PermalinkView from 'components/permalink_view/permalink_view';
|
||||
|
||||
import TestHelper from 'packages/mattermost-redux/test/test_helper';
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {act, waitFor} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, waitFor} from 'tests/react_testing_utils';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
import {joinPrivateChannelPrompt} from 'utils/channel_utils';
|
||||
@@ -104,15 +102,15 @@ describe('components/PermalinkView', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', async () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PermalinkView {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call baseProps.actions.focusPost on doPermalinkEvent', async () => {
|
||||
mountWithIntl(
|
||||
renderWithContext(
|
||||
<PermalinkView {...baseProps}/>,
|
||||
);
|
||||
|
||||
@@ -123,15 +121,18 @@ describe('components/PermalinkView', () => {
|
||||
});
|
||||
|
||||
test('should call baseProps.actions.focusPost when postid changes', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
const {rerender} = renderWithContext(
|
||||
<PermalinkView {...baseProps}/>,
|
||||
);
|
||||
|
||||
const newPostid = `${baseProps.match.params.postid}_new`;
|
||||
|
||||
act(() => {
|
||||
wrapper.setProps({...baseProps, match: {params: {postid: newPostid}}} as any);
|
||||
});
|
||||
rerender(
|
||||
<PermalinkView
|
||||
{...baseProps}
|
||||
match={{params: {postid: newPostid}} as match<{postid: string}>}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(baseProps.actions.focusPost).toHaveBeenCalledTimes(2);
|
||||
@@ -142,13 +143,13 @@ describe('components/PermalinkView', () => {
|
||||
test('should match snapshot with archived channel', async () => {
|
||||
const props = {...baseProps, channelIsArchived: true};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
const {container} = renderWithContext(
|
||||
<PermalinkView {...props}/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
|
||||
+112
-184
@@ -1,60 +1,42 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/post_view/embedded_bindings/embedded_binding should match snapshot 1`] = `
|
||||
<div
|
||||
className="attachment"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="attachment__content"
|
||||
class="attachment"
|
||||
>
|
||||
<div
|
||||
className="clearfix attachment__container attachment__container--"
|
||||
class="attachment__content"
|
||||
>
|
||||
<h1
|
||||
className="attachment__title"
|
||||
<div
|
||||
class="clearfix attachment__container attachment__container--"
|
||||
>
|
||||
<Connect(Markdown)
|
||||
message="some text"
|
||||
options={
|
||||
Object {
|
||||
"mentionHighlight": false,
|
||||
"renderer": LinkOnlyRenderer {
|
||||
"options": Object {},
|
||||
},
|
||||
}
|
||||
}
|
||||
postId="post_id"
|
||||
/>
|
||||
</h1>
|
||||
<div>
|
||||
<div
|
||||
className="attachment__body attachment__body--no_thumb"
|
||||
<h1
|
||||
class="attachment__title"
|
||||
>
|
||||
<Connect(ShowMore)
|
||||
isAttachmentText={true}
|
||||
maxHeight={200}
|
||||
text="some title"
|
||||
some text
|
||||
</h1>
|
||||
<div>
|
||||
<div
|
||||
class="attachment__body attachment__body--no_thumb"
|
||||
>
|
||||
<Connect(Markdown)
|
||||
imageProps={
|
||||
Object {
|
||||
"onImageHeightChanged": [Function],
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
message="some title"
|
||||
postId="post_id"
|
||||
/>
|
||||
</Connect(ShowMore)>
|
||||
<div
|
||||
class="post-message post-message--collapsed"
|
||||
>
|
||||
<div
|
||||
class="post-message__text-container"
|
||||
style="max-height: 200px;"
|
||||
>
|
||||
<p>
|
||||
some title
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="clear: both;"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"clear": "both",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,60 +44,42 @@ exports[`components/post_view/embedded_bindings/embedded_binding should match sn
|
||||
`;
|
||||
|
||||
exports[`components/post_view/embedded_bindings/embedded_binding should match snapshot when the attachment has a link in the title 1`] = `
|
||||
<div
|
||||
className="attachment"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="attachment__content"
|
||||
class="attachment"
|
||||
>
|
||||
<div
|
||||
className="clearfix attachment__container attachment__container--"
|
||||
class="attachment__content"
|
||||
>
|
||||
<h1
|
||||
className="attachment__title"
|
||||
<div
|
||||
class="clearfix attachment__container attachment__container--"
|
||||
>
|
||||
<Connect(Markdown)
|
||||
message="some text"
|
||||
options={
|
||||
Object {
|
||||
"mentionHighlight": false,
|
||||
"renderer": LinkOnlyRenderer {
|
||||
"options": Object {},
|
||||
},
|
||||
}
|
||||
}
|
||||
postId="post_id"
|
||||
/>
|
||||
</h1>
|
||||
<div>
|
||||
<div
|
||||
className="attachment__body attachment__body--no_thumb"
|
||||
<h1
|
||||
class="attachment__title"
|
||||
>
|
||||
<Connect(ShowMore)
|
||||
isAttachmentText={true}
|
||||
maxHeight={200}
|
||||
text="some title"
|
||||
some text
|
||||
</h1>
|
||||
<div>
|
||||
<div
|
||||
class="attachment__body attachment__body--no_thumb"
|
||||
>
|
||||
<Connect(Markdown)
|
||||
imageProps={
|
||||
Object {
|
||||
"onImageHeightChanged": [Function],
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
message="some title"
|
||||
postId="post_id"
|
||||
/>
|
||||
</Connect(ShowMore)>
|
||||
<div
|
||||
class="post-message post-message--collapsed"
|
||||
>
|
||||
<div
|
||||
class="post-message__text-container"
|
||||
style="max-height: 200px;"
|
||||
>
|
||||
<p>
|
||||
some title
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="clear: both;"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"clear": "both",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -123,60 +87,42 @@ exports[`components/post_view/embedded_bindings/embedded_binding should match sn
|
||||
`;
|
||||
|
||||
exports[`components/post_view/embedded_bindings/embedded_binding should match snapshot when the attachment has an emoji in the title 1`] = `
|
||||
<div
|
||||
className="attachment"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="attachment__content"
|
||||
class="attachment"
|
||||
>
|
||||
<div
|
||||
className="clearfix attachment__container attachment__container--"
|
||||
class="attachment__content"
|
||||
>
|
||||
<h1
|
||||
className="attachment__title"
|
||||
<div
|
||||
class="clearfix attachment__container attachment__container--"
|
||||
>
|
||||
<Connect(Markdown)
|
||||
message="some text"
|
||||
options={
|
||||
Object {
|
||||
"mentionHighlight": false,
|
||||
"renderer": LinkOnlyRenderer {
|
||||
"options": Object {},
|
||||
},
|
||||
}
|
||||
}
|
||||
postId="post_id"
|
||||
/>
|
||||
</h1>
|
||||
<div>
|
||||
<div
|
||||
className="attachment__body attachment__body--no_thumb"
|
||||
<h1
|
||||
class="attachment__title"
|
||||
>
|
||||
<Connect(ShowMore)
|
||||
isAttachmentText={true}
|
||||
maxHeight={200}
|
||||
text="some title"
|
||||
some text
|
||||
</h1>
|
||||
<div>
|
||||
<div
|
||||
class="attachment__body attachment__body--no_thumb"
|
||||
>
|
||||
<Connect(Markdown)
|
||||
imageProps={
|
||||
Object {
|
||||
"onImageHeightChanged": [Function],
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
message="some title"
|
||||
postId="post_id"
|
||||
/>
|
||||
</Connect(ShowMore)>
|
||||
<div
|
||||
class="post-message post-message--collapsed"
|
||||
>
|
||||
<div
|
||||
class="post-message__text-container"
|
||||
style="max-height: 200px;"
|
||||
>
|
||||
<p>
|
||||
some title
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="clear: both;"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"clear": "both",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -184,60 +130,42 @@ exports[`components/post_view/embedded_bindings/embedded_binding should match sn
|
||||
`;
|
||||
|
||||
exports[`components/post_view/embedded_bindings/embedded_binding should match snapshot when the attachment hasn't any emojis in the title 1`] = `
|
||||
<div
|
||||
className="attachment"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="attachment__content"
|
||||
class="attachment"
|
||||
>
|
||||
<div
|
||||
className="clearfix attachment__container attachment__container--"
|
||||
class="attachment__content"
|
||||
>
|
||||
<h1
|
||||
className="attachment__title"
|
||||
<div
|
||||
class="clearfix attachment__container attachment__container--"
|
||||
>
|
||||
<Connect(Markdown)
|
||||
message="some text"
|
||||
options={
|
||||
Object {
|
||||
"mentionHighlight": false,
|
||||
"renderer": LinkOnlyRenderer {
|
||||
"options": Object {},
|
||||
},
|
||||
}
|
||||
}
|
||||
postId="post_id"
|
||||
/>
|
||||
</h1>
|
||||
<div>
|
||||
<div
|
||||
className="attachment__body attachment__body--no_thumb"
|
||||
<h1
|
||||
class="attachment__title"
|
||||
>
|
||||
<Connect(ShowMore)
|
||||
isAttachmentText={true}
|
||||
maxHeight={200}
|
||||
text="some title"
|
||||
some text
|
||||
</h1>
|
||||
<div>
|
||||
<div
|
||||
class="attachment__body attachment__body--no_thumb"
|
||||
>
|
||||
<Connect(Markdown)
|
||||
imageProps={
|
||||
Object {
|
||||
"onImageHeightChanged": [Function],
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
message="some title"
|
||||
postId="post_id"
|
||||
/>
|
||||
</Connect(ShowMore)>
|
||||
<div
|
||||
class="post-message post-message--collapsed"
|
||||
>
|
||||
<div
|
||||
class="post-message__text-container"
|
||||
style="max-height: 200px;"
|
||||
>
|
||||
<p>
|
||||
some title
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="clear: both;"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"clear": "both",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+22
-9
@@ -1,13 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import type {AppBinding} from '@mattermost/types/apps';
|
||||
import type {MessageAttachment as MessageAttachmentType} from '@mattermost/types/message_attachments';
|
||||
import type {Post} from '@mattermost/types/posts';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import EmbeddedBinding from './embedded_binding';
|
||||
|
||||
describe('components/post_view/embedded_bindings/embedded_binding', () => {
|
||||
@@ -29,9 +30,21 @@ describe('components/post_view/embedded_bindings/embedded_binding', () => {
|
||||
currentRelativeTeamUrl: 'dummy_team',
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
entities: {
|
||||
general: {config: {}},
|
||||
users: {profiles: {}},
|
||||
groups: {myGroups: []},
|
||||
emojis: {},
|
||||
channels: {},
|
||||
teams: {teams: {}},
|
||||
preferences: {myPreferences: {}},
|
||||
},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(<EmbeddedBinding {...baseProps}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<EmbeddedBinding {...baseProps}/>, initialState);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when the attachment has an emoji in the title', () => {
|
||||
@@ -42,9 +55,9 @@ describe('components/post_view/embedded_bindings/embedded_binding', () => {
|
||||
} as MessageAttachmentType,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<EmbeddedBinding {...props}/>);
|
||||
const {container} = renderWithContext(<EmbeddedBinding {...props}/>, initialState);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when the attachment hasn\'t any emojis in the title', () => {
|
||||
@@ -55,9 +68,9 @@ describe('components/post_view/embedded_bindings/embedded_binding', () => {
|
||||
} as MessageAttachmentType,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<EmbeddedBinding {...props}/>);
|
||||
const {container} = renderWithContext(<EmbeddedBinding {...props}/>, initialState);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when the attachment has a link in the title', () => {
|
||||
@@ -68,8 +81,8 @@ describe('components/post_view/embedded_bindings/embedded_binding', () => {
|
||||
} as MessageAttachmentType,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<EmbeddedBinding {...props}/>);
|
||||
const {container} = renderWithContext(<EmbeddedBinding {...props}/>, initialState);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
+56
-19
@@ -1,15 +1,36 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import type {AppBinding, AppCallResponse} from '@mattermost/types/apps';
|
||||
import type {Post} from '@mattermost/types/posts';
|
||||
|
||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {act, renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
|
||||
import SelectBinding, {RawSelectBinding} from './select_binding';
|
||||
import SelectBinding from './select_binding';
|
||||
|
||||
type MockOnSelected = ((selected: {text: string; value: string}) => void) | undefined;
|
||||
|
||||
let mockOnSelected: MockOnSelected;
|
||||
|
||||
jest.mock('components/autocomplete_selector', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: (props: any) => {
|
||||
mockOnSelected = props.onSelected;
|
||||
return (
|
||||
<div data-testid='autoCompleteSelector'>
|
||||
<input
|
||||
placeholder={props.placeholder}
|
||||
value={props.value || ''}
|
||||
readOnly={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('components/post_view/embedded_bindings/select_binding', () => {
|
||||
const post = {
|
||||
@@ -73,16 +94,30 @@ describe('components/post_view/embedded_bindings/select_binding', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const intl = {
|
||||
formatMessage: (message: {id: string; defaultMessage: string}) => {
|
||||
return message.defaultMessage;
|
||||
const initialState = {
|
||||
entities: {
|
||||
general: {config: {}},
|
||||
users: {profiles: {}},
|
||||
groups: {myGroups: []},
|
||||
emojis: {},
|
||||
channels: {},
|
||||
teams: {teams: {}},
|
||||
preferences: {myPreferences: {}},
|
||||
},
|
||||
} as any;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnSelected = undefined;
|
||||
});
|
||||
|
||||
test('should start with nothing selected', () => {
|
||||
const wrapper = shallowWithIntl(<SelectBinding {...baseProps}/>);
|
||||
renderWithContext(<SelectBinding {...baseProps}/>, initialState);
|
||||
|
||||
expect(wrapper.state()).toMatchObject({});
|
||||
const selector = screen.getByTestId('autoCompleteSelector');
|
||||
expect(selector).toBeInTheDocument();
|
||||
|
||||
const input = selector.querySelector('input');
|
||||
expect(input).toHaveValue('');
|
||||
});
|
||||
|
||||
describe('handleSelected', () => {
|
||||
@@ -109,14 +144,15 @@ describe('components/post_view/embedded_bindings/select_binding', () => {
|
||||
postEphemeralCallResponseForPost: jest.fn(),
|
||||
openAppsModal: jest.fn(),
|
||||
},
|
||||
intl,
|
||||
};
|
||||
|
||||
const wrapper = shallow<RawSelectBinding>(<RawSelectBinding {...props}/>);
|
||||
renderWithContext(<SelectBinding {...props}/>, initialState);
|
||||
|
||||
await wrapper.instance().handleSelected({
|
||||
text: 'Option 1',
|
||||
value: 'option1',
|
||||
await act(async () => {
|
||||
mockOnSelected!({
|
||||
text: 'Option 1',
|
||||
value: 'option1',
|
||||
});
|
||||
});
|
||||
|
||||
expect(props.actions.getChannel).toHaveBeenCalledWith('some_channel_id');
|
||||
@@ -167,14 +203,15 @@ describe('components/post_view/embedded_bindings/select_binding', () => {
|
||||
postEphemeralCallResponseForPost: jest.fn(),
|
||||
openAppsModal: jest.fn(),
|
||||
},
|
||||
intl,
|
||||
};
|
||||
|
||||
const wrapper = shallow<RawSelectBinding>(<RawSelectBinding {...props}/>);
|
||||
renderWithContext(<SelectBinding {...props}/>, initialState);
|
||||
|
||||
await wrapper.instance().handleSelected({
|
||||
text: 'Option 1',
|
||||
value: 'option1',
|
||||
await act(async () => {
|
||||
mockOnSelected!({
|
||||
text: 'Option 1',
|
||||
value: 'option1',
|
||||
});
|
||||
});
|
||||
|
||||
expect(props.actions.postEphemeralCallResponseForPost).toHaveBeenCalledWith(errorResponse, 'The error', post);
|
||||
|
||||
+22
-56
@@ -1,76 +1,42 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/post_view/post_list snapshot for loading when there are no posts 1`] = `
|
||||
<div
|
||||
className="post-list-holder-by-time"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="post-list__table"
|
||||
class="post-list-holder-by-time"
|
||||
>
|
||||
<div
|
||||
className="post-list__content"
|
||||
id="virtualizedPostListContent"
|
||||
class="post-list__table"
|
||||
>
|
||||
<PostList
|
||||
actions={
|
||||
Object {
|
||||
"canLoadMorePosts": [Function],
|
||||
"changeUnreadChunkTimeStamp": [MockFunction],
|
||||
"loadNewerPosts": [Function],
|
||||
"loadOlderPosts": [Function],
|
||||
"toggleShouldStartFromBottomWhenUnread": [MockFunction],
|
||||
"updateNewMessagesAtInChannel": [MockFunction],
|
||||
}
|
||||
}
|
||||
atLatestPost={false}
|
||||
autoRetryEnable={true}
|
||||
channelId="fake-id"
|
||||
isChannelAutotranslated={false}
|
||||
isMobileView={false}
|
||||
lastViewedAt={1532345226632}
|
||||
loadingNewerPosts={false}
|
||||
loadingOlderPosts={false}
|
||||
postListIds={Array []}
|
||||
shouldStartFromBottomWhenUnread={false}
|
||||
/>
|
||||
<div
|
||||
class="post-list__content"
|
||||
id="virtualizedPostListContent"
|
||||
>
|
||||
<div
|
||||
data-testid="virt-post-list"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/post_view/post_list snapshot with couple of posts 1`] = `
|
||||
<div
|
||||
className="post-list-holder-by-time"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="post-list__table"
|
||||
class="post-list-holder-by-time"
|
||||
>
|
||||
<div
|
||||
className="post-list__content"
|
||||
id="virtualizedPostListContent"
|
||||
class="post-list__table"
|
||||
>
|
||||
<PostList
|
||||
actions={
|
||||
Object {
|
||||
"canLoadMorePosts": [Function],
|
||||
"changeUnreadChunkTimeStamp": [MockFunction],
|
||||
"loadNewerPosts": [Function],
|
||||
"loadOlderPosts": [Function],
|
||||
"toggleShouldStartFromBottomWhenUnread": [MockFunction],
|
||||
"updateNewMessagesAtInChannel": [MockFunction],
|
||||
}
|
||||
}
|
||||
atLatestPost={false}
|
||||
autoRetryEnable={true}
|
||||
channelId="fake-id"
|
||||
isChannelAutotranslated={false}
|
||||
isMobileView={false}
|
||||
lastViewedAt={1532345226632}
|
||||
loadingNewerPosts={false}
|
||||
loadingOlderPosts={false}
|
||||
postListIds={Array []}
|
||||
shouldStartFromBottomWhenUnread={false}
|
||||
/>
|
||||
<div
|
||||
class="post-list__content"
|
||||
id="virtualizedPostListContent"
|
||||
>
|
||||
<div
|
||||
data-testid="virt-post-list"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,43 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import VirtPostList from 'components/post_view/post_list_virtualized/post_list_virtualized';
|
||||
|
||||
import {renderWithContext, act, runPostRenderAct} from 'tests/react_testing_utils';
|
||||
import {PostRequestTypes} from 'utils/constants';
|
||||
|
||||
import PostList, {MAX_EXTRA_PAGES_LOADED} from './post_list';
|
||||
|
||||
let lastVirtPostListProps: any = null;
|
||||
jest.mock('components/post_view/post_list_virtualized/post_list_virtualized', () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => {
|
||||
lastVirtPostListProps = props;
|
||||
return <div data-testid='virt-post-list'/>;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('components/loading_screen', () => ({
|
||||
__esModule: true,
|
||||
default: ({centered}: {centered?: boolean}) => (
|
||||
<div
|
||||
data-testid='loading-screen'
|
||||
data-centered={centered}
|
||||
>{'Loading'}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('actions/telemetry_actions.jsx', () => ({
|
||||
clearMarks: jest.fn(),
|
||||
mark: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('utils/performance_telemetry', () => ({
|
||||
Mark: {},
|
||||
Measure: {},
|
||||
measureAndReport: jest.fn(),
|
||||
}));
|
||||
|
||||
const actionsProp = {
|
||||
loadPostsAround: jest.fn().mockImplementation(() => Promise.resolve({atLatestMessage: true, atOldestmessage: true})),
|
||||
loadUnreads: jest.fn().mockImplementation(() => Promise.resolve({atLatestMessage: true, atOldestmessage: true})),
|
||||
@@ -51,163 +79,288 @@ const baseProps = {
|
||||
};
|
||||
|
||||
describe('components/post_view/post_list', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
lastVirtPostListProps = null;
|
||||
});
|
||||
|
||||
it('snapshot for loading when there are no posts', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostList {...{...baseProps, postListIds: []}}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('snapshot with couple of posts', () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostList {...{...baseProps, postListIds: postIds}}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Should call postsOnLoad', async () => {
|
||||
const emptyPostList: string[] = [];
|
||||
|
||||
const wrapper = shallow<PostList>(
|
||||
<PostList {...{...baseProps, postListIds: emptyPostList}}/>,
|
||||
const ref = React.createRef<PostList>();
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, postListIds: emptyPostList}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(actionsProp.loadUnreads).toHaveBeenCalledWith(baseProps.channelId);
|
||||
await wrapper.instance().postsOnLoad('undefined');
|
||||
expect(wrapper.state('loadingNewerPosts')).toBe(false);
|
||||
expect(wrapper.state('loadingOlderPosts')).toBe(false);
|
||||
await act(async () => {
|
||||
await ref.current!.postsOnLoad('undefined');
|
||||
});
|
||||
expect(ref.current!.state.loadingNewerPosts).toBe(false);
|
||||
expect(ref.current!.state.loadingOlderPosts).toBe(false);
|
||||
});
|
||||
|
||||
it('Should not call loadUnreads if isPrefetchingInProcess is true', async () => {
|
||||
const emptyPostList: string[] = [];
|
||||
|
||||
shallow(<PostList {...{...baseProps, postListIds: emptyPostList, isPrefetchingInProcess: true}}/>);
|
||||
renderWithContext(
|
||||
<PostList
|
||||
{...{...baseProps, postListIds: emptyPostList, isPrefetchingInProcess: true}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(actionsProp.loadUnreads).not.toHaveBeenCalledWith(baseProps.channelId);
|
||||
});
|
||||
|
||||
it('Should call for before and afterPosts', async () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
const wrapper = shallow<PostList>(
|
||||
<PostList {...{...baseProps, postListIds: postIds}}/>,
|
||||
const ref = React.createRef<PostList>();
|
||||
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, postListIds: postIds}}
|
||||
/>,
|
||||
);
|
||||
|
||||
wrapper.find(VirtPostList).prop('actions').loadOlderPosts();
|
||||
expect(wrapper.state('loadingOlderPosts')).toEqual(true);
|
||||
expect(actionsProp.loadPosts).toHaveBeenCalledWith({channelId: baseProps.channelId, postId: postIds[postIds.length - 1], type: PostRequestTypes.BEFORE_ID, perPage: 30});
|
||||
await wrapper.instance().callLoadPosts('undefined', 'undefined', undefined, 30);
|
||||
expect(wrapper.state('loadingOlderPosts')).toBe(false);
|
||||
await runPostRenderAct();
|
||||
|
||||
wrapper.find(VirtPostList).prop('actions').loadNewerPosts();
|
||||
expect(wrapper.state('loadingNewerPosts')).toEqual(true);
|
||||
// Use deferred promise to capture intermediate loading state
|
||||
let resolveOlderLoadPosts!: (value: any) => void;
|
||||
actionsProp.loadPosts.mockImplementationOnce(() => new Promise((resolve) => {
|
||||
resolveOlderLoadPosts = resolve;
|
||||
}));
|
||||
|
||||
await act(async () => {
|
||||
lastVirtPostListProps.actions.loadOlderPosts();
|
||||
});
|
||||
expect(ref.current!.state.loadingOlderPosts).toEqual(true);
|
||||
expect(actionsProp.loadPosts).toHaveBeenCalledWith({channelId: baseProps.channelId, postId: postIds[postIds.length - 1], type: PostRequestTypes.BEFORE_ID, perPage: 30});
|
||||
await act(async () => {
|
||||
resolveOlderLoadPosts({moreToLoad: false});
|
||||
});
|
||||
expect(ref.current!.state.loadingOlderPosts).toBe(false);
|
||||
|
||||
let resolveNewerLoadPosts!: (value: any) => void;
|
||||
actionsProp.loadPosts.mockImplementationOnce(() => new Promise((resolve) => {
|
||||
resolveNewerLoadPosts = resolve;
|
||||
}));
|
||||
|
||||
await act(async () => {
|
||||
lastVirtPostListProps.actions.loadNewerPosts();
|
||||
});
|
||||
expect(ref.current!.state.loadingNewerPosts).toEqual(true);
|
||||
expect(actionsProp.loadPosts).toHaveBeenCalledWith({channelId: baseProps.channelId, postId: postIds[0], type: PostRequestTypes.AFTER_ID, perPage: 30});
|
||||
await wrapper.instance().callLoadPosts('undefined', 'undefined', undefined, 30);
|
||||
expect(wrapper.state('loadingNewerPosts')).toBe(false);
|
||||
await act(async () => {
|
||||
resolveNewerLoadPosts({moreToLoad: false});
|
||||
});
|
||||
expect(ref.current!.state.loadingNewerPosts).toBe(false);
|
||||
});
|
||||
|
||||
it('VirtPostList Should have formattedPostIds as prop', async () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<PostList {...{...baseProps, postListIds: postIds}}/>,
|
||||
);
|
||||
|
||||
const formattedPostIds = wrapper.find(VirtPostList).prop('postListIds');
|
||||
expect(formattedPostIds).toEqual([]);
|
||||
expect(lastVirtPostListProps.postListIds).toEqual([]);
|
||||
});
|
||||
|
||||
it('getOldestVisiblePostId and getLatestVisiblePostId should return based on postListIds', async () => {
|
||||
const postIds = createFakePosIds(10);
|
||||
const formattedPostIds = ['1', '2'];
|
||||
const wrapper = shallow<PostList>(
|
||||
<PostList {...{...baseProps, postListIds: postIds, formattedPostIds}}/>,
|
||||
const ref = React.createRef<PostList>();
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, postListIds: postIds, formattedPostIds}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
expect(instance.getOldestVisiblePostId()).toEqual('123410');
|
||||
expect(instance.getLatestVisiblePostId()).toEqual('12341');
|
||||
expect(ref.current!.getOldestVisiblePostId()).toEqual('123410');
|
||||
expect(ref.current!.getLatestVisiblePostId()).toEqual('12341');
|
||||
});
|
||||
|
||||
it('Should call for permalink posts', async () => {
|
||||
const focusedPostId = 'new';
|
||||
const wrapper = shallow(
|
||||
<PostList {...{...baseProps, focusedPostId}}/>,
|
||||
const ref = React.createRef<PostList>();
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, focusedPostId}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(actionsProp.loadPostsAround).toHaveBeenCalledWith(baseProps.channelId, focusedPostId);
|
||||
await actionsProp.loadPostsAround();
|
||||
expect(wrapper.state('loadingOlderPosts')).toBe(false);
|
||||
expect(wrapper.state('loadingNewerPosts')).toBe(false);
|
||||
await act(async () => {
|
||||
await actionsProp.loadPostsAround();
|
||||
});
|
||||
expect(ref.current!.state.loadingOlderPosts).toBe(false);
|
||||
expect(ref.current!.state.loadingNewerPosts).toBe(false);
|
||||
});
|
||||
|
||||
it('Should call for loadLatestPosts', async () => {
|
||||
const wrapper = shallow(
|
||||
<PostList {...{...baseProps, postListIds: [], isFirstLoad: false}}/>,
|
||||
const ref = React.createRef<PostList>();
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, postListIds: [], isFirstLoad: false}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(actionsProp.loadLatestPosts).toHaveBeenCalledWith(baseProps.channelId);
|
||||
await actionsProp.loadLatestPosts();
|
||||
expect(wrapper.state('loadingOlderPosts')).toBe(false);
|
||||
expect(wrapper.state('loadingNewerPosts')).toBe(false);
|
||||
await act(async () => {
|
||||
await actionsProp.loadLatestPosts();
|
||||
});
|
||||
expect(ref.current!.state.loadingOlderPosts).toBe(false);
|
||||
expect(ref.current!.state.loadingNewerPosts).toBe(false);
|
||||
});
|
||||
|
||||
describe('getPostsSince', () => {
|
||||
test('should call getPostsSince on channel switch', () => {
|
||||
test('should call getPostsSince on channel switch', async () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
shallow(<PostList {...{...baseProps, isFirstLoad: false, postListIds: postIds, latestPostTimeStamp: 1234}}/>);
|
||||
renderWithContext(
|
||||
<PostList
|
||||
{...{...baseProps, isFirstLoad: false, postListIds: postIds, latestPostTimeStamp: 1234}}
|
||||
/>,
|
||||
);
|
||||
expect(actionsProp.syncPostsInChannel).toHaveBeenCalledWith(baseProps.channelId, 1234, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('canLoadMorePosts', () => {
|
||||
test('Should not call loadLatestPosts if postListIds is empty', async () => {
|
||||
const wrapper = shallow(<PostList {...{...baseProps, isFirstLoad: false, postListIds: []}}/>);
|
||||
const ref = React.createRef<PostList>();
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, isFirstLoad: false, postListIds: []}}
|
||||
/>,
|
||||
);
|
||||
expect(actionsProp.loadLatestPosts).toHaveBeenCalledWith(baseProps.channelId);
|
||||
await actionsProp.loadLatestPosts();
|
||||
expect(wrapper.state('loadingOlderPosts')).toBe(false);
|
||||
expect(wrapper.state('loadingNewerPosts')).toBe(false);
|
||||
await act(async () => {
|
||||
await actionsProp.loadLatestPosts();
|
||||
});
|
||||
expect(ref.current!.state.loadingOlderPosts).toBe(false);
|
||||
expect(ref.current!.state.loadingNewerPosts).toBe(false);
|
||||
});
|
||||
|
||||
test('Should not call loadPosts if olderPosts or newerPosts are loading', async () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
const wrapper = shallow(<PostList {...{...baseProps, isFirstLoad: false, postListIds: postIds}}/>);
|
||||
wrapper.setState({loadingOlderPosts: true});
|
||||
wrapper.find(VirtPostList).prop('actions').canLoadMorePosts(undefined);
|
||||
const ref = React.createRef<PostList>();
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, isFirstLoad: false, postListIds: postIds}}
|
||||
/>,
|
||||
);
|
||||
act(() => {
|
||||
ref.current!.setState({loadingOlderPosts: true});
|
||||
});
|
||||
await act(async () => {
|
||||
lastVirtPostListProps.actions.canLoadMorePosts(undefined);
|
||||
});
|
||||
expect(actionsProp.loadPosts).not.toHaveBeenCalled();
|
||||
wrapper.setState({loadingOlderPosts: false});
|
||||
wrapper.setState({loadingNewerPosts: true});
|
||||
wrapper.find(VirtPostList).prop('actions').canLoadMorePosts(undefined);
|
||||
act(() => {
|
||||
ref.current!.setState({loadingOlderPosts: false});
|
||||
});
|
||||
act(() => {
|
||||
ref.current!.setState({loadingNewerPosts: true});
|
||||
});
|
||||
await act(async () => {
|
||||
lastVirtPostListProps.actions.canLoadMorePosts(undefined);
|
||||
});
|
||||
expect(actionsProp.loadPosts).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Should not call loadPosts if there were more than MAX_EXTRA_PAGES_LOADED', async () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
const wrapper = shallow<PostList>(<PostList {...{...baseProps, isFirstLoad: false, postListIds: postIds}}/>);
|
||||
wrapper.instance().extraPagesLoaded = MAX_EXTRA_PAGES_LOADED + 1;
|
||||
wrapper.find(VirtPostList).prop('actions').canLoadMorePosts(undefined);
|
||||
const ref = React.createRef<PostList>();
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, isFirstLoad: false, postListIds: postIds}}
|
||||
/>,
|
||||
);
|
||||
ref.current!.extraPagesLoaded = MAX_EXTRA_PAGES_LOADED + 1;
|
||||
await act(async () => {
|
||||
lastVirtPostListProps.actions.canLoadMorePosts(undefined);
|
||||
});
|
||||
expect(actionsProp.loadPosts).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Should call getPostsBefore if not all older posts are loaded', async () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
const wrapper = shallow(<PostList {...{...baseProps, isFirstLoad: false, postListIds: postIds}}/>);
|
||||
wrapper.setProps({atOldestPost: false});
|
||||
wrapper.find(VirtPostList).prop('actions').canLoadMorePosts(undefined);
|
||||
const ref = React.createRef<PostList>();
|
||||
const {rerender} = renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, isFirstLoad: false, postListIds: postIds}}
|
||||
/>,
|
||||
);
|
||||
await act(async () => {});
|
||||
rerender(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, isFirstLoad: false, postListIds: postIds, atOldestPost: false}}
|
||||
/>,
|
||||
);
|
||||
await act(async () => {
|
||||
lastVirtPostListProps.actions.canLoadMorePosts(undefined);
|
||||
});
|
||||
expect(actionsProp.loadPosts).toHaveBeenCalledWith({channelId: baseProps.channelId, postId: postIds[postIds.length - 1], type: PostRequestTypes.BEFORE_ID, perPage: 200});
|
||||
});
|
||||
|
||||
test('Should call getPostsAfter if all older posts are loaded and not newerPosts', async () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
const wrapper = shallow(<PostList {...{...baseProps, isFirstLoad: false, postListIds: postIds}}/>);
|
||||
wrapper.setProps({atOldestPost: true});
|
||||
wrapper.find(VirtPostList).prop('actions').canLoadMorePosts(undefined);
|
||||
const ref = React.createRef<PostList>();
|
||||
const {rerender} = renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, isFirstLoad: false, postListIds: postIds}}
|
||||
/>,
|
||||
);
|
||||
await act(async () => {});
|
||||
rerender(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, isFirstLoad: false, postListIds: postIds, atOldestPost: true}}
|
||||
/>,
|
||||
);
|
||||
await act(async () => {
|
||||
lastVirtPostListProps.actions.canLoadMorePosts(undefined);
|
||||
});
|
||||
expect(actionsProp.loadPosts).toHaveBeenCalledWith({channelId: baseProps.channelId, postId: postIds[0], type: PostRequestTypes.AFTER_ID, perPage: 30});
|
||||
});
|
||||
|
||||
test('Should call getPostsAfter canLoadMorePosts is requested with AFTER_ID', async () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
const wrapper = shallow(<PostList {...{...baseProps, isFirstLoad: false, postListIds: postIds}}/>);
|
||||
wrapper.find(VirtPostList).prop('actions').canLoadMorePosts(PostRequestTypes.AFTER_ID);
|
||||
renderWithContext(
|
||||
<PostList
|
||||
{...{...baseProps, isFirstLoad: false, postListIds: postIds}}
|
||||
/>,
|
||||
);
|
||||
await act(async () => {
|
||||
lastVirtPostListProps.actions.canLoadMorePosts(PostRequestTypes.AFTER_ID);
|
||||
});
|
||||
expect(actionsProp.loadPosts).toHaveBeenCalledWith({channelId: baseProps.channelId, postId: postIds[0], type: PostRequestTypes.AFTER_ID, perPage: 30});
|
||||
});
|
||||
});
|
||||
@@ -215,7 +368,14 @@ describe('components/post_view/post_list', () => {
|
||||
describe('Auto retry of load more posts', () => {
|
||||
test('Should retry loadPosts on failure of loadPosts', async () => {
|
||||
const postIds = createFakePosIds(2);
|
||||
const loadPosts = jest.fn().mockImplementation(() => Promise.resolve({moreToLoad: true, error: {}}));
|
||||
|
||||
// Use deferred first call to capture intermediate loading state
|
||||
let resolveFirstCall!: (value: any) => void;
|
||||
const loadPosts = jest.fn().
|
||||
mockImplementationOnce(() => new Promise((resolve) => {
|
||||
resolveFirstCall = resolve;
|
||||
})).
|
||||
mockImplementation(() => Promise.resolve({moreToLoad: true, error: {}}));
|
||||
const props = {
|
||||
...baseProps,
|
||||
postListIds: postIds,
|
||||
@@ -225,17 +385,31 @@ describe('components/post_view/post_list', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<PostList {...props}/>,
|
||||
const ref = React.createRef<PostList>();
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
wrapper.find(VirtPostList).prop('actions').loadOlderPosts();
|
||||
expect(wrapper.state('loadingOlderPosts')).toEqual(true);
|
||||
await runPostRenderAct();
|
||||
|
||||
await act(async () => {
|
||||
lastVirtPostListProps.actions.loadOlderPosts();
|
||||
});
|
||||
expect(ref.current!.state.loadingOlderPosts).toEqual(true);
|
||||
expect(loadPosts).toHaveBeenCalledTimes(1);
|
||||
expect(loadPosts).toHaveBeenCalledWith({channelId: baseProps.channelId, postId: postIds[postIds.length - 1], type: PostRequestTypes.BEFORE_ID, perPage: 30});
|
||||
await loadPosts();
|
||||
expect(wrapper.state('loadingOlderPosts')).toBe(false);
|
||||
expect(loadPosts).toHaveBeenCalledTimes(3);
|
||||
|
||||
// Resolve first call with error to trigger retries
|
||||
await act(async () => {
|
||||
resolveFirstCall({moreToLoad: true, error: {}});
|
||||
});
|
||||
expect(ref.current!.state.loadingOlderPosts).toBe(false);
|
||||
|
||||
// 1 original + 3 retries = 4 total calls
|
||||
expect(loadPosts).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -243,21 +417,31 @@ describe('components/post_view/post_list', () => {
|
||||
test('Should call markChannelAsReadAndViewed on postsOnLoad', async () => {
|
||||
const emptyPostList: string[] = [];
|
||||
|
||||
const wrapper = shallow<PostList>(
|
||||
<PostList {...{...baseProps, postListIds: emptyPostList}}/>,
|
||||
const ref = React.createRef<PostList>();
|
||||
renderWithContext(
|
||||
<PostList
|
||||
ref={ref}
|
||||
{...{...baseProps, postListIds: emptyPostList}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await wrapper.instance().postsOnLoad('undefined');
|
||||
await act(async () => {
|
||||
await ref.current!.postsOnLoad('undefined');
|
||||
});
|
||||
expect(actionsProp.markChannelAsRead).toHaveBeenCalledWith(baseProps.channelId);
|
||||
});
|
||||
test('Should not call markChannelAsReadAndViewed as it is a permalink', async () => {
|
||||
const emptyPostList: string[] = [];
|
||||
const focusedPostId = 'new';
|
||||
shallow(
|
||||
<PostList {...{...baseProps, postListIds: emptyPostList, focusedPostId}}/>,
|
||||
renderWithContext(
|
||||
<PostList
|
||||
{...{...baseProps, postListIds: emptyPostList, focusedPostId}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await actionsProp.loadPostsAround();
|
||||
await act(async () => {
|
||||
await actionsProp.loadPostsAround();
|
||||
});
|
||||
expect(actionsProp.markChannelAsRead).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -275,12 +459,14 @@ describe('components/post_view/post_list', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<PostList {...props}/>,
|
||||
);
|
||||
|
||||
// Trigger user scroll up
|
||||
wrapper.find(VirtPostList).prop('actions').loadOlderPosts();
|
||||
// Trigger user scroll up (async setState + loadPosts; wrap in act, not render)
|
||||
await act(async () => {
|
||||
await lastVirtPostListProps.actions.loadOlderPosts();
|
||||
});
|
||||
|
||||
expect(loadPosts).toHaveBeenCalledWith({
|
||||
channelId: baseProps.channelId,
|
||||
@@ -303,12 +489,14 @@ describe('components/post_view/post_list', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<PostList {...props}/>,
|
||||
);
|
||||
|
||||
// Trigger auto-loading via canLoadMorePosts
|
||||
await wrapper.find(VirtPostList).prop('actions').canLoadMorePosts(PostRequestTypes.BEFORE_ID);
|
||||
await act(async () => {
|
||||
await lastVirtPostListProps.actions.canLoadMorePosts(PostRequestTypes.BEFORE_ID);
|
||||
});
|
||||
|
||||
expect(loadPosts).toHaveBeenCalledWith({
|
||||
channelId: baseProps.channelId,
|
||||
@@ -330,12 +518,13 @@ describe('components/post_view/post_list', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<PostList {...props}/>,
|
||||
);
|
||||
|
||||
// Trigger user scroll down
|
||||
wrapper.find(VirtPostList).prop('actions').loadNewerPosts();
|
||||
await act(async () => {
|
||||
await lastVirtPostListProps.actions.loadNewerPosts();
|
||||
});
|
||||
|
||||
expect(loadPosts).toHaveBeenCalledWith({
|
||||
channelId: baseProps.channelId,
|
||||
@@ -358,12 +547,13 @@ describe('components/post_view/post_list', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<PostList {...props}/>,
|
||||
);
|
||||
|
||||
// Trigger auto-loading via canLoadMorePosts
|
||||
wrapper.find(VirtPostList).prop('actions').canLoadMorePosts(PostRequestTypes.BEFORE_ID);
|
||||
await act(async () => {
|
||||
await lastVirtPostListProps.actions.canLoadMorePosts(PostRequestTypes.BEFORE_ID);
|
||||
});
|
||||
|
||||
// Should use auto-load page size (200 posts)
|
||||
expect(loadPosts).toHaveBeenCalledWith(expect.objectContaining({
|
||||
|
||||
+86
-99
@@ -1,140 +1,127 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/post_view/post_list_row should have class hideAnimation for NEWER_MESSAGES_LOADER if loadingNewerPosts is false 1`] = `
|
||||
<div
|
||||
className="loading-screen"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="loading__content hideAnimation"
|
||||
class="loading-screen"
|
||||
>
|
||||
<div
|
||||
className="round round-1"
|
||||
/>
|
||||
<div
|
||||
className="round round-2"
|
||||
/>
|
||||
<div
|
||||
className="round round-3"
|
||||
/>
|
||||
class="loading__content hideAnimation"
|
||||
>
|
||||
<div
|
||||
class="round round-1"
|
||||
/>
|
||||
<div
|
||||
class="round round-2"
|
||||
/>
|
||||
<div
|
||||
class="round round-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/post_view/post_list_row should have class hideAnimation for OLDER_MESSAGES_LOADER if loadingOlderPosts is false 1`] = `
|
||||
<div
|
||||
className="loading-screen"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="loading__content hideAnimation"
|
||||
class="loading-screen"
|
||||
>
|
||||
<div
|
||||
className="round round-1"
|
||||
/>
|
||||
<div
|
||||
className="round round-2"
|
||||
/>
|
||||
<div
|
||||
className="round round-3"
|
||||
/>
|
||||
class="loading__content hideAnimation"
|
||||
>
|
||||
<div
|
||||
class="round round-1"
|
||||
/>
|
||||
<div
|
||||
class="round round-2"
|
||||
/>
|
||||
<div
|
||||
class="round round-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/post_view/post_list_row should render channel intro message 1`] = `<Connect(ChannelIntroMessage) />`;
|
||||
exports[`components/post_view/post_list_row should render channel intro message 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-testid="channel-intro-message"
|
||||
>
|
||||
ChannelIntroMessage
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/post_view/post_list_row should render combined post 1`] = `
|
||||
<Connect(Connect(ErrorBoundary(PostComponent)))
|
||||
combinedId="user-activity-1234-5678"
|
||||
isChannelAutotranslated={false}
|
||||
isLastPost={false}
|
||||
location="CENTER"
|
||||
previousPostId="abcd"
|
||||
shouldHighlight={false}
|
||||
togglePostMenu={[MockFunction]}
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
data-testid="combined-user-activity"
|
||||
>
|
||||
CombinedUserActivityPost: user-activity-1234-5678
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/post_view/post_list_row should render date line 1`] = `
|
||||
<Memo(DateSeparator)
|
||||
date={1553106600000}
|
||||
key="1553106600000"
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
data-testid="date-separator"
|
||||
>
|
||||
DateSeparator: 1553106600000
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/post_view/post_list_row should render manual load messages trigger 1`] = `
|
||||
<button
|
||||
className="more-messages-text theme style--none color--link"
|
||||
onClick={[MockFunction]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Load more messages"
|
||||
id="posts_view.loadMore"
|
||||
/>
|
||||
</button>
|
||||
<div>
|
||||
<button
|
||||
class="more-messages-text theme style--none color--link"
|
||||
>
|
||||
Load more messages
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/post_view/post_list_row should render more messages loading indicator 1`] = `
|
||||
<div
|
||||
className="loading-screen"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="loading__content"
|
||||
class="loading-screen"
|
||||
>
|
||||
<div
|
||||
className="round round-1"
|
||||
/>
|
||||
<div
|
||||
className="round round-2"
|
||||
/>
|
||||
<div
|
||||
className="round round-3"
|
||||
/>
|
||||
class="loading__content"
|
||||
>
|
||||
<div
|
||||
class="round round-1"
|
||||
/>
|
||||
<div
|
||||
class="round round-2"
|
||||
/>
|
||||
<div
|
||||
class="round round-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/post_view/post_list_row should render new messages line 1`] = `
|
||||
<Memo(NewMessageSeparator)
|
||||
channelId="channel_id_1"
|
||||
newMessagesSeparatorActions={Array []}
|
||||
separatorId="start-of-new-messages-1553106600000"
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
data-testid="new-message-separator"
|
||||
>
|
||||
NewMessageSeparator: start-of-new-messages-1553106600000
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/post_view/post_list_row should render post 1`] = `
|
||||
<Connect(ErrorBoundary(PostComponent))
|
||||
isChannelAutotranslated={false}
|
||||
isLastPost={false}
|
||||
location="CENTER"
|
||||
post={
|
||||
Object {
|
||||
"channel_id": "",
|
||||
"create_at": 0,
|
||||
"delete_at": 0,
|
||||
"edit_at": 0,
|
||||
"hashtags": "",
|
||||
"id": "post_id_1",
|
||||
"is_pinned": false,
|
||||
"message": "post message",
|
||||
"metadata": Object {
|
||||
"embeds": Array [],
|
||||
"emojis": Array [],
|
||||
"files": Array [],
|
||||
"images": Object {},
|
||||
"reactions": Array [],
|
||||
},
|
||||
"original_id": "",
|
||||
"pending_post_id": "",
|
||||
"props": Object {},
|
||||
"reply_count": 0,
|
||||
"root_id": "",
|
||||
"type": "system_add_remove",
|
||||
"update_at": 0,
|
||||
"user_id": "user_id",
|
||||
}
|
||||
}
|
||||
previousPostId="abcd"
|
||||
shouldHighlight={false}
|
||||
togglePostMenu={[MockFunction]}
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
data-testid="post"
|
||||
>
|
||||
Post: post_id_1
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import type {ChannelType} from '@mattermost/types/channels';
|
||||
@@ -9,17 +8,42 @@ import type {CloudUsage} from '@mattermost/types/cloud';
|
||||
|
||||
import * as PostListUtils from 'mattermost-redux/utils/post_list';
|
||||
|
||||
import Post from 'components/post';
|
||||
import ChannelIntroMessage from 'components/post_view/channel_intro_message/';
|
||||
import CombinedUserActivityPost from 'components/post_view/combined_user_activity_post';
|
||||
import DateSeparator from 'components/post_view/date_separator';
|
||||
import NewMessageSeparator from 'components/post_view/new_message_separator/new_message_separator';
|
||||
|
||||
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
|
||||
import {PostListRowListIds} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import PostListRow from './post_list_row';
|
||||
|
||||
jest.mock('components/post', () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => <div data-testid='post'>{`Post: ${props.post?.id}`}</div>,
|
||||
}));
|
||||
|
||||
jest.mock('components/post_view/channel_intro_message/', () => ({
|
||||
__esModule: true,
|
||||
default: () => <div data-testid='channel-intro-message'>{'ChannelIntroMessage'}</div>,
|
||||
}));
|
||||
|
||||
jest.mock('components/post_view/combined_user_activity_post', () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => <div data-testid='combined-user-activity'>{`CombinedUserActivityPost: ${props.combinedId}`}</div>,
|
||||
}));
|
||||
|
||||
jest.mock('components/post_view/date_separator', () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => <div data-testid='date-separator'>{`DateSeparator: ${props.date}`}</div>,
|
||||
}));
|
||||
|
||||
jest.mock('components/post_view/new_message_separator/new_message_separator', () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => <div data-testid='new-message-separator'>{`NewMessageSeparator: ${props.separatorId}`}</div>,
|
||||
}));
|
||||
|
||||
jest.mock('components/center_message_lock', () => ({
|
||||
__esModule: true,
|
||||
default: () => <div data-testid='center-message-lock'>{'CenterMessageLock'}</div>,
|
||||
}));
|
||||
|
||||
describe('components/post_view/post_list_row', () => {
|
||||
const defaultProps = {
|
||||
listId: '1234',
|
||||
@@ -52,13 +76,13 @@ describe('components/post_view/post_list_row', () => {
|
||||
listId,
|
||||
loadingOlderPosts: true,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostListRow {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render manual load messages trigger', () => {
|
||||
test('should render manual load messages trigger', async () => {
|
||||
const listId = PostListRowListIds.LOAD_OLDER_MESSAGES_TRIGGER;
|
||||
const loadOlderPosts = jest.fn();
|
||||
const props = {
|
||||
@@ -66,11 +90,11 @@ describe('components/post_view/post_list_row', () => {
|
||||
listId,
|
||||
loadOlderPosts,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostListRow {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
wrapper.prop('onClick')();
|
||||
expect(container).toMatchSnapshot();
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
expect(loadOlderPosts).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -99,11 +123,11 @@ describe('components/post_view/post_list_row', () => {
|
||||
listId,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostListRow {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find(ChannelIntroMessage).exists()).toBe(true);
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(screen.getByTestId('channel-intro-message')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render new messages line', () => {
|
||||
@@ -112,11 +136,11 @@ describe('components/post_view/post_list_row', () => {
|
||||
...defaultProps,
|
||||
listId,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostListRow {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find(NewMessageSeparator).exists()).toBe(true);
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(screen.getByTestId('new-message-separator')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render date line', () => {
|
||||
@@ -125,11 +149,11 @@ describe('components/post_view/post_list_row', () => {
|
||||
...defaultProps,
|
||||
listId,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostListRow {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find(DateSeparator).exists()).toBe(true);
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(screen.getByTestId('date-separator')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render combined post', () => {
|
||||
@@ -139,11 +163,11 @@ describe('components/post_view/post_list_row', () => {
|
||||
listId: `${PostListUtils.COMBINED_USER_ACTIVITY}1234-5678`,
|
||||
previousListId: 'abcd',
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostListRow {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find(CombinedUserActivityPost).exists()).toBe(true);
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(screen.getByTestId('combined-user-activity')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render post', () => {
|
||||
@@ -153,11 +177,11 @@ describe('components/post_view/post_list_row', () => {
|
||||
listId: '1234',
|
||||
previousListId: 'abcd',
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostListRow {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find(Post).exists()).toBe(true);
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(screen.getByTestId('post')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should have class hideAnimation for OLDER_MESSAGES_LOADER if loadingOlderPosts is false', () => {
|
||||
@@ -167,10 +191,10 @@ describe('components/post_view/post_list_row', () => {
|
||||
listId,
|
||||
loadingOlderPosts: false,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostListRow {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have class hideAnimation for NEWER_MESSAGES_LOADER if loadingNewerPosts is false', () => {
|
||||
@@ -180,9 +204,9 @@ describe('components/post_view/post_list_row', () => {
|
||||
listId,
|
||||
loadingNewerPosts: false,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<PostListRow {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
+519
-189
File diff suppressed because it is too large
Load Diff
@@ -208,32 +208,28 @@ describe('ProductNoticesModal', () => {
|
||||
Date.now = jest.fn().mockReturnValue(1599868800000);
|
||||
|
||||
// Simulate socket disconnect
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<ProductNoticesModal
|
||||
ref={ref}
|
||||
{...baseProps}
|
||||
socketStatus={{
|
||||
...baseProps.socketStatus,
|
||||
connected: false,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<ProductNoticesModal
|
||||
ref={ref}
|
||||
{...baseProps}
|
||||
socketStatus={{
|
||||
...baseProps.socketStatus,
|
||||
connected: false,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Simulate socket reconnect
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<ProductNoticesModal
|
||||
ref={ref}
|
||||
{...baseProps}
|
||||
socketStatus={{
|
||||
...baseProps.socketStatus,
|
||||
connected: true,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<ProductNoticesModal
|
||||
ref={ref}
|
||||
{...baseProps}
|
||||
socketStatus={{
|
||||
...baseProps.socketStatus,
|
||||
connected: true,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(baseProps.actions.getInProductNotices).toHaveBeenCalledWith(baseProps.currentTeamId, 'web', baseProps.version);
|
||||
expect(baseProps.actions.getInProductNotices).toHaveBeenCalledTimes(2);
|
||||
@@ -257,32 +253,28 @@ describe('ProductNoticesModal', () => {
|
||||
Date.now = jest.fn().mockReturnValue(1599760196593);
|
||||
|
||||
// Simulate socket disconnect
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<ProductNoticesModal
|
||||
ref={ref}
|
||||
{...baseProps}
|
||||
socketStatus={{
|
||||
...baseProps.socketStatus,
|
||||
connected: false,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<ProductNoticesModal
|
||||
ref={ref}
|
||||
{...baseProps}
|
||||
socketStatus={{
|
||||
...baseProps.socketStatus,
|
||||
connected: false,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Simulate socket reconnect on the same day
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<ProductNoticesModal
|
||||
ref={ref}
|
||||
{...baseProps}
|
||||
socketStatus={{
|
||||
...baseProps.socketStatus,
|
||||
connected: true,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<ProductNoticesModal
|
||||
ref={ref}
|
||||
{...baseProps}
|
||||
socketStatus={{
|
||||
...baseProps.socketStatus,
|
||||
connected: true,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(baseProps.actions.getInProductNotices).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -1,38 +1,19 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/RhsThread should match snapshot 1`] = `
|
||||
<div
|
||||
className="sidebar-right__body"
|
||||
id="rhsContainer"
|
||||
>
|
||||
<Connect(injectIntl(RhsHeaderPost))
|
||||
channel={
|
||||
Object {
|
||||
"create_at": 0,
|
||||
"creator_id": "",
|
||||
"delete_at": 0,
|
||||
"display_name": "",
|
||||
"group_constrained": false,
|
||||
"header": "",
|
||||
"id": "channel_id",
|
||||
"last_post_at": 0,
|
||||
"last_root_post_at": 0,
|
||||
"name": "",
|
||||
"purpose": "",
|
||||
"scheme_id": "",
|
||||
"status": "",
|
||||
"team_id": "team_id",
|
||||
"teammate_id": "",
|
||||
"type": "O",
|
||||
"update_at": 0,
|
||||
}
|
||||
}
|
||||
rootPostId="id"
|
||||
/>
|
||||
<Connect(ThreadViewer)
|
||||
isThreadView={false}
|
||||
rootPostId="id"
|
||||
useRelativeTimestamp={true}
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
class="sidebar-right__body"
|
||||
id="rhsContainer"
|
||||
>
|
||||
<div
|
||||
data-root-post-id="id"
|
||||
data-testid="rhs-header-post"
|
||||
/>
|
||||
<div
|
||||
data-root-post-id="id"
|
||||
data-testid="thread-viewer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Post} from '@mattermost/types/posts';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import RhsThread from './rhs_thread';
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
let mockState: any;
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux') as typeof import('react-redux'),
|
||||
useSelector: (selector: (state: typeof mockState) => unknown) => selector(mockState),
|
||||
useDispatch: () => mockDispatch,
|
||||
jest.mock('components/rhs_header_post', () => (props: any) => (
|
||||
<div
|
||||
data-testid='rhs-header-post'
|
||||
data-root-post-id={props.rootPostId}
|
||||
/>
|
||||
));
|
||||
jest.mock('components/threading/thread_viewer', () => (props: any) => (
|
||||
<div
|
||||
data-testid='thread-viewer'
|
||||
data-root-post-id={props.rootPostId}
|
||||
/>
|
||||
));
|
||||
jest.mock('actions/views/rhs', () => ({
|
||||
closeRightHandSide: jest.fn(() => ({type: 'CLOSE_RHS'})),
|
||||
}));
|
||||
|
||||
describe('components/RhsThread', () => {
|
||||
@@ -39,34 +45,18 @@ describe('components/RhsThread', () => {
|
||||
status: '',
|
||||
});
|
||||
|
||||
const actions = {
|
||||
removePost: jest.fn(),
|
||||
selectPostCard: jest.fn(),
|
||||
getPostThread: jest.fn(),
|
||||
};
|
||||
|
||||
const directTeammate: UserProfile = TestHelper.getUserMock();
|
||||
|
||||
const currentTeam = TestHelper.getTeamMock();
|
||||
|
||||
const baseProps = {
|
||||
posts: [post],
|
||||
selected: post,
|
||||
channel,
|
||||
currentUserId: 'user_id',
|
||||
previewCollapsed: 'false',
|
||||
previewEnabled: true,
|
||||
socketConnectionStatus: true,
|
||||
actions,
|
||||
directTeammate,
|
||||
currentTeam,
|
||||
fromSuppressed: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<RhsThread {...baseProps}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
+27
-36
@@ -1,47 +1,38 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`CommandSuggestion should match snapshot 1`] = `
|
||||
<SuggestionContainer
|
||||
aria-describedby="test-suggestion-description"
|
||||
aria-labelledby="test-suggestion-label"
|
||||
id="test-suggestion"
|
||||
isSelection={true}
|
||||
item={
|
||||
Object {
|
||||
"Complete": "/invite",
|
||||
"Description": "Invite a user to a channel",
|
||||
"Hint": "@[username] ~[channel]",
|
||||
"IconData": "",
|
||||
"Suggestion": "/invite",
|
||||
}
|
||||
}
|
||||
matchedPretext=""
|
||||
onClick={[MockFunction]}
|
||||
onMouseMove={[MockFunction]}
|
||||
term="/"
|
||||
>
|
||||
<div
|
||||
className="slash-command__icon"
|
||||
>
|
||||
<span>
|
||||
/
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="slash-command__info"
|
||||
<div>
|
||||
<li
|
||||
aria-describedby="test-suggestion-description"
|
||||
aria-labelledby="test-suggestion-label"
|
||||
class="suggestion-list__item suggestion--selected"
|
||||
id="test-suggestion"
|
||||
role="option"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
className="slash-command__title"
|
||||
id="test-suggestion-label"
|
||||
class="slash-command__icon"
|
||||
>
|
||||
invite @[username] ~[channel]
|
||||
<span>
|
||||
/
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="slash-command__desc"
|
||||
id="test-suggestion-description"
|
||||
class="slash-command__info"
|
||||
>
|
||||
Invite a user to a channel
|
||||
<div
|
||||
class="slash-command__title"
|
||||
id="test-suggestion-label"
|
||||
>
|
||||
invite @[username] ~[channel]
|
||||
</div>
|
||||
<div
|
||||
class="slash-command__desc"
|
||||
id="test-suggestion-description"
|
||||
>
|
||||
Invite a user to a channel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SuggestionContainer>
|
||||
</li>
|
||||
</div>
|
||||
`;
|
||||
|
||||
+6
-4
@@ -1,13 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import type {AutocompleteSuggestion} from '@mattermost/types/integrations';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import * as UserAgent from 'utils/user_agent';
|
||||
|
||||
import CommandProvider, {commandsGroup, CommandSuggestion} from './command_provider';
|
||||
@@ -32,12 +32,14 @@ describe('CommandSuggestion', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<CommandSuggestion {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find('.slash-command__title').first().text()).toEqual('invite @[username] ~[channel]');
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
const titleElement = container.querySelector('.slash-command__title');
|
||||
expect(titleElement?.textContent).toEqual('invite @[username] ~[channel]');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,64 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Textbox, {type Props} from 'components/textbox/textbox';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
jest.mock('components/suggestion/suggestion_box', () => {
|
||||
// Use require inside the factory: jest hoists mocks before imports run, and babel-plugin-jest-hoist
|
||||
// forbids non-mock-prefixed outer variables (e.g. React). Cast so forwardRef accepts generics (TS2347).
|
||||
const react = require('react') as typeof import('react'); // eslint-disable-line @typescript-eslint/no-var-requires, global-require
|
||||
const MockSuggestionBox = react.forwardRef<HTMLTextAreaElement, any>((props: any, ref: any) => (
|
||||
<textarea
|
||||
ref={ref}
|
||||
id={props.id}
|
||||
className={props.className}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
disabled={props.disabled}
|
||||
data-testid='suggestion-box'
|
||||
readOnly={true}
|
||||
style={props.style}
|
||||
/>
|
||||
));
|
||||
MockSuggestionBox.displayName = 'SuggestionBox';
|
||||
return {__esModule: true, default: MockSuggestionBox};
|
||||
});
|
||||
|
||||
jest.mock('components/post_markdown', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: (props: any) => <div data-testid='post-markdown'>{props.message}</div>,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('components/suggestion/suggestion_list', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('components/suggestion/at_mention_provider', () => {
|
||||
return jest.fn().mockImplementation(() => ({setProps: jest.fn()}));
|
||||
});
|
||||
jest.mock('components/suggestion/channel_mention_provider', () => {
|
||||
return jest.fn().mockImplementation(() => ({setProps: jest.fn()}));
|
||||
});
|
||||
jest.mock('components/suggestion/command_provider/command_provider', () => {
|
||||
return jest.fn().mockImplementation(() => ({setProps: jest.fn()}));
|
||||
});
|
||||
jest.mock('components/suggestion/command_provider/app_provider', () => {
|
||||
return jest.fn().mockImplementation(() => ({setProps: jest.fn()}));
|
||||
});
|
||||
jest.mock('components/suggestion/emoticon_provider', () => {
|
||||
return jest.fn().mockImplementation(() => ({}));
|
||||
});
|
||||
|
||||
jest.mock('components/remove_flagged_message_confirmation_modal/remove_flagged_message_confirmation_modal', () => {
|
||||
return jest.fn(() => <div data-testid='keep-remove-flagged-message-confirmation-modal'>{'KeepRemoveFlaggedMessageConfirmationModal Mock'}</div>);
|
||||
});
|
||||
@@ -52,10 +103,10 @@ describe('components/TextBox', () => {
|
||||
supportsCommands: false,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<Textbox {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with additional, optional props', () => {
|
||||
@@ -85,10 +136,10 @@ describe('components/TextBox', () => {
|
||||
openWhenEmpty: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<Textbox {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should throw error when value is too long', () => {
|
||||
@@ -112,12 +163,12 @@ describe('components/TextBox', () => {
|
||||
handlePostError,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<Textbox {...props}/>,
|
||||
);
|
||||
|
||||
expect(gotError).toEqual(true);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should throw error when new property is too long', () => {
|
||||
@@ -141,14 +192,20 @@ describe('components/TextBox', () => {
|
||||
handlePostError,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
const {container, rerender} = renderWithContext(
|
||||
<Textbox {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.setProps({value: 'some test text that exceeds char limit'});
|
||||
wrapper.update();
|
||||
expect(gotError).toEqual(true);
|
||||
const newProps = {
|
||||
...props,
|
||||
value: 'some test text that exceeds char limit',
|
||||
};
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
rerender(
|
||||
<Textbox {...newProps}/>,
|
||||
);
|
||||
|
||||
expect(gotError).toEqual(true);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
+93
-90
@@ -1,110 +1,113 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/threading/global_threads/thread_list should match snapshot 1`] = `
|
||||
<div
|
||||
className="ThreadList"
|
||||
id="threads-list-container"
|
||||
tabIndex={0}
|
||||
>
|
||||
<Header
|
||||
heading={
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="ThreadList"
|
||||
id="threads-list-container"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-label="Filter visible threads"
|
||||
aria-orientation="horizontal"
|
||||
className="tab-buttons-list"
|
||||
role="tablist"
|
||||
class="Header"
|
||||
id="tutorial-threads-mobile-header"
|
||||
>
|
||||
<div
|
||||
className="tab-button-wrapper"
|
||||
class="left"
|
||||
>
|
||||
<Memo(Button)
|
||||
aria-controls="threads-list"
|
||||
aria-selected={true}
|
||||
className="Button___large Margined"
|
||||
id="threads-list-filter-none"
|
||||
isActive={true}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
role="tab"
|
||||
tabIndex={0}
|
||||
<div
|
||||
aria-label="Filter visible threads"
|
||||
aria-orientation="horizontal"
|
||||
class="tab-buttons-list"
|
||||
role="tablist"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Followed threads"
|
||||
id="globalThreads.heading"
|
||||
/>
|
||||
</Memo(Button)>
|
||||
<div
|
||||
class="tab-button-wrapper"
|
||||
>
|
||||
<button
|
||||
aria-controls="threads-list"
|
||||
aria-selected="true"
|
||||
class="Button Button___transparent is-active Button___large Margined"
|
||||
id="threads-list-filter-none"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="Button_label"
|
||||
>
|
||||
Followed threads
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tab-button-wrapper"
|
||||
id="threads-list-unread-button"
|
||||
>
|
||||
<button
|
||||
aria-controls="threads-list"
|
||||
aria-selected="false"
|
||||
class="Button Button___transparent Button___large Margined"
|
||||
id="threads-list-filter-unread"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="Button_label"
|
||||
>
|
||||
Unreads
|
||||
<span
|
||||
class="dot"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="tab-button-wrapper"
|
||||
id="threads-list-unread-button"
|
||||
class="spacer"
|
||||
/>
|
||||
<div
|
||||
class="right-anchor"
|
||||
>
|
||||
<Memo(Button)
|
||||
aria-controls="threads-list"
|
||||
aria-selected={false}
|
||||
className="Button___large Margined"
|
||||
hasDot={true}
|
||||
id="threads-list-filter-unread"
|
||||
isActive={false}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
role="tab"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Unreads"
|
||||
id="threading.filters.unreads"
|
||||
/>
|
||||
</Memo(Button)>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
id="tutorial-threads-mobile-header"
|
||||
right={
|
||||
<div
|
||||
className="right-anchor"
|
||||
>
|
||||
<WithTooltip
|
||||
title="Mark all threads as read"
|
||||
>
|
||||
<Memo(Button)
|
||||
<button
|
||||
aria-label="Mark all threads as read"
|
||||
className="Button___large Button___icon"
|
||||
class="Button Button___transparent Button___large Button___icon"
|
||||
id="threads-list__mark-all-as-read"
|
||||
marginTop={true}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
className="icon"
|
||||
class="Button_label margin_top"
|
||||
>
|
||||
<PlaylistCheckIcon
|
||||
size={18}
|
||||
/>
|
||||
<span
|
||||
class="icon"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="18"
|
||||
version="1.1"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14,10H2V12H14V10M14,6H2V8H14V6M2,16H10V14H2V16M21.5,11.5L23,13L16,20L11.5,15.5L13,14L16,17L21.5,11.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</Memo(Button)>
|
||||
</WithTooltip>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="threads"
|
||||
data-testid="threads_list"
|
||||
id="threads-list"
|
||||
role="tabpanel"
|
||||
>
|
||||
<Memo(VirtualizedThreadList)
|
||||
addNoMoreResultsItem={false}
|
||||
ids={
|
||||
Array [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
]
|
||||
}
|
||||
isLoading={false}
|
||||
key="threads_list_"
|
||||
loadMoreItems={[Function]}
|
||||
total={0}
|
||||
/>
|
||||
<div
|
||||
class="threads"
|
||||
data-testid="threads_list"
|
||||
id="threads-list"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
data-testid="virtualized-thread-list"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
+33
-3
@@ -1,7 +1,37 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/threading/global_threads/thread_list/virtualized_thread_list should match snapshot 1`] = `
|
||||
<AutoSizer>
|
||||
<Component />
|
||||
</AutoSizer>
|
||||
<body>
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
class="virtualized-thread-list"
|
||||
style="position: relative; height: 500px; width: 300px; overflow: auto; -webkit-overflow-scrolling: touch; will-change: auto; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
style="height: 399px; width: 100%;"
|
||||
>
|
||||
<div
|
||||
data-testid="thread-row-1"
|
||||
style="position: absolute; left: 0px; top: 0px; height: 133px; width: 100%;"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
<div
|
||||
data-testid="thread-row-2"
|
||||
style="position: absolute; left: 0px; top: 133px; height: 133px; width: 100%;"
|
||||
>
|
||||
2
|
||||
</div>
|
||||
<div
|
||||
data-testid="thread-row-3"
|
||||
style="position: absolute; left: 0px; top: 266px; height: 133px; width: 100%;"
|
||||
>
|
||||
3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
+194
-47
@@ -1,57 +1,204 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/threading/global_threads/thread_list/virtualized_thread_list_row should match snapshot 1`] = `
|
||||
<Connect(Component)
|
||||
isFirstThreadInList={false}
|
||||
isSelected={false}
|
||||
key="2"
|
||||
style={Object {}}
|
||||
threadId="2"
|
||||
/>
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
data-selected="false"
|
||||
data-testid="thread-item-2"
|
||||
/>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`components/threading/global_threads/thread_list/virtualized_thread_list_row should support item loading indicator 1`] = `
|
||||
<LoadingScreen
|
||||
message={<React.Fragment />}
|
||||
style={Object {}}
|
||||
/>
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="loading-screen"
|
||||
style="position: relative;"
|
||||
>
|
||||
<div
|
||||
class="loading__content"
|
||||
>
|
||||
<p />
|
||||
<div
|
||||
class="round round-1"
|
||||
/>
|
||||
<div
|
||||
class="round round-2"
|
||||
/>
|
||||
<div
|
||||
class="round round-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`components/threading/global_threads/thread_list/virtualized_thread_list_row should support item search guidance 1`] = `
|
||||
<NoResultsIndicator
|
||||
iconGraphic={
|
||||
<SearchSVG
|
||||
className="no-results__icon"
|
||||
/>
|
||||
}
|
||||
layout={1}
|
||||
style={
|
||||
Object {
|
||||
"background": "rgba(var(--center-channel-color-rgb), 0.04)",
|
||||
"padding": "16px 16px 16px 24px",
|
||||
}
|
||||
}
|
||||
subtitle={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="If you’re looking for older conversations, try searching with {searchShortcut}"
|
||||
id="globalThreads.searchGuidance.subtitle"
|
||||
values={
|
||||
Object {
|
||||
"searchShortcut": <SearchShortcut
|
||||
className="thread-no-results-subtitle-shortcut"
|
||||
variant="contrast"
|
||||
/>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
subtitleClassName="thread-no-results-subtitle"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="That’s the end of the list"
|
||||
id="globalThreads.searchGuidance.title"
|
||||
/>
|
||||
}
|
||||
titleClassName="thread-no-results-title"
|
||||
/>
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="no-results__wrapper horizontal-layout"
|
||||
style="padding: 16px 16px 16px 24px; background: rgba(var(--center-channel-color-rgb), 0.04);"
|
||||
>
|
||||
<span
|
||||
class="no-results__icon"
|
||||
>
|
||||
<svg
|
||||
aria-label="Search Icon"
|
||||
height="99"
|
||||
role="img"
|
||||
viewBox="0 0 123 99"
|
||||
width="123"
|
||||
>
|
||||
<g>
|
||||
<rect
|
||||
fill="var(--button-bg)"
|
||||
fill-opacity="0.12"
|
||||
height="31"
|
||||
rx="3.75"
|
||||
width="107"
|
||||
x="0.255005"
|
||||
y="32"
|
||||
/>
|
||||
<rect
|
||||
fill="var(--button-bg)"
|
||||
fill-opacity="0.12"
|
||||
height="27"
|
||||
rx="3.75"
|
||||
width="76"
|
||||
x="46.255"
|
||||
/>
|
||||
<rect
|
||||
fill="var(--button-bg)"
|
||||
fill-opacity="0.12"
|
||||
height="27"
|
||||
rx="3.75"
|
||||
width="76"
|
||||
x="46.255"
|
||||
y="67"
|
||||
/>
|
||||
<path
|
||||
d="M62.5814 6.60593C70.262 6.55738 77.3232 8.54646 83.755 12.5732C86.3956 14.2264 88.9301 16.223 91.3579 18.5631C94.3589 21.6787 96.7911 24.9905 98.653 28.5M62.5813 87.8542C53.6129 87.7863 45.4964 85.1063 38.255 79.7911C36.7377 78.6774 35.2587 77.4479 33.8185 76.1025C30.0784 72.0903 27.2249 67.8234 25.255 63.299M21.9572 47.2301C21.964 41.8688 22.9467 36.7921 24.9051 32C26.8843 27.1573 29.8599 22.6053 33.8322 18.3439M101.949 37C102.786 40.2642 103.206 43.6739 103.206 47.2301C103.2 51.5308 102.585 55.6211 101.358 59.5M100.495 32.5C100.769 33.1979 101.022 33.903 101.255 34.6153"
|
||||
fill="none"
|
||||
stroke="var(--center-channel-color)"
|
||||
stroke-opacity="0.48"
|
||||
/>
|
||||
<path
|
||||
d="M82.6551 5.72396C90.4991 9.4225 96.7179 14.8934 101.302 22.1318C103.184 25.1036 104.79 28.3733 106.12 31.9406"
|
||||
fill="none"
|
||||
stroke="var(--center-channel-color)"
|
||||
stroke-opacity="0.48"
|
||||
/>
|
||||
<rect
|
||||
fill="var(--center-channel-color)"
|
||||
fill-opacity="0.48"
|
||||
height="7.45395"
|
||||
rx="1"
|
||||
transform="rotate(45 89.8878 69.3516)"
|
||||
width="33.9752"
|
||||
x="89.8878"
|
||||
y="69.3516"
|
||||
/>
|
||||
<rect
|
||||
fill="none"
|
||||
height="6.45395"
|
||||
rx="0.5"
|
||||
stroke="var(--center-channel-color)"
|
||||
stroke-opacity="0.8"
|
||||
transform="rotate(45 89.8878 70.0587)"
|
||||
width="32.9752"
|
||||
x="89.8878"
|
||||
y="70.0587"
|
||||
/>
|
||||
<path
|
||||
d="M62.5706 11.2232C72.1732 11.1625 80.6832 14.6952 88.0764 21.8213C95.0689 29.0809 98.5773 37.5423 98.5773 47.2299C98.5652 56.9053 95.0689 65.3789 88.0764 72.6385C80.6832 79.7646 72.1975 83.2973 62.5706 83.2366C52.9559 83.1637 44.4459 79.7039 37.077 72.8206C30.0724 65.3061 26.5761 56.7839 26.564 47.2299C26.5761 37.6759 30.0845 29.1416 37.0892 21.627C44.4702 14.7559 52.968 11.2839 62.5706 11.2232Z"
|
||||
fill="var(--center-channel-bg)"
|
||||
/>
|
||||
<path
|
||||
d="M62.5738 11.7232C72.0335 11.6634 80.418 15.1368 87.7227 22.1748C94.6257 29.3444 98.0773 37.6809 98.0773 47.2299C98.0652 56.7673 94.6251 65.1159 87.7228 72.2849C80.418 79.323 72.0576 82.7964 62.5738 82.7366C53.0959 82.6646 44.7093 79.2606 37.431 72.467C30.5135 65.0406 27.0759 56.6419 27.064 47.2294C27.0762 37.8176 30.5255 29.4072 37.4428 21.9809C44.7342 15.1984 53.109 11.783 62.5738 11.7232Z"
|
||||
fill="none"
|
||||
stroke="var(--center-channel-color)"
|
||||
stroke-opacity="0.8"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M88.076 21.8213C80.6829 14.6952 72.1729 11.1625 62.5703 11.2232C52.9677 11.2839 44.4698 14.7559 37.0888 21.627C30.0841 29.1416 26.5757 37.6759 26.5636 47.2299C26.5757 56.7839 30.072 65.3061 37.0767 72.8206C44.4455 79.7039 52.9555 83.1638 62.5703 83.2366C72.1971 83.2973 80.6829 79.7646 88.076 72.6385C95.0685 65.3789 98.5648 56.9053 98.577 47.2299C98.577 37.5423 95.0685 29.0809 88.076 21.8213ZM84.5637 25.3086C78.1852 19.1606 70.8432 16.1128 62.5586 16.1652C54.274 16.2175 46.9424 19.213 40.5745 25.1411C34.5312 31.6242 31.5043 38.9872 31.4939 47.2299C31.5043 55.4726 34.5207 62.8251 40.564 69.3083C46.9215 75.2468 54.2635 78.2318 62.5586 78.2947C70.8642 78.347 78.1852 75.2992 84.5637 69.1512C90.5965 62.888 93.6129 55.5774 93.6233 47.2299C93.6233 38.872 90.5965 31.5719 84.5637 25.3086Z"
|
||||
fill="var(--center-channel-bg)"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M88.076 21.8213C80.6828 14.6952 72.1728 11.1625 62.5702 11.2232C52.9676 11.2839 44.4697 14.7559 37.0887 21.627C30.0841 29.1416 26.5757 37.6759 26.5635 47.2299C26.5757 56.7839 30.0719 65.3061 37.0766 72.8206C44.4455 79.7039 52.9555 83.1638 62.5702 83.2366C72.1971 83.2973 80.6828 79.7646 88.076 72.6385C95.0685 65.3789 98.5648 56.9053 98.5769 47.2299C98.5769 37.5423 95.0685 29.0809 88.076 21.8213ZM84.5636 25.3086C78.1852 19.1606 70.8432 16.1128 62.5585 16.1652C54.2739 16.2175 46.9424 19.213 40.5744 25.1411C34.5312 31.6242 31.5043 38.9872 31.4938 47.2299C31.5043 55.4726 34.5207 62.8251 40.564 69.3083C46.9214 75.2468 54.2634 78.2318 62.5585 78.2947C70.8641 78.347 78.1852 75.2992 84.5636 69.1512C90.5964 62.888 93.6128 55.5774 93.6233 47.2299C93.6233 38.872 90.5964 31.5719 84.5636 25.3086Z"
|
||||
fill="var(--center-channel-color)"
|
||||
fill-opacity="0.8"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M62.5736 11.7232C72.0333 11.6634 80.4179 15.1369 87.7225 22.1748C94.6255 29.3444 98.0771 37.681 98.0771 47.2299C98.065 56.7674 94.625 65.1159 87.7226 72.2849C80.4178 79.3231 72.0575 82.7964 62.5736 82.7366C53.0957 82.6647 44.7092 79.2607 37.4308 72.4671C30.5133 65.0407 27.0758 56.642 27.0638 47.2294C27.076 37.8176 30.5254 29.4072 37.4426 21.9809C44.734 15.1984 53.1088 11.783 62.5736 11.7232Z"
|
||||
fill="none"
|
||||
stroke="var(--center-channel-color)"
|
||||
stroke-opacity="0.8"
|
||||
/>
|
||||
<circle
|
||||
cx="51.255"
|
||||
cy="36"
|
||||
fill="var(--center-channel-color)"
|
||||
fill-opacity="0.32"
|
||||
r="9"
|
||||
/>
|
||||
<path
|
||||
d="M43.255 56H85.255"
|
||||
fill="none"
|
||||
stroke="var(--center-channel-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-opacity="0.8"
|
||||
/>
|
||||
<path
|
||||
d="M43.255 63H65.255"
|
||||
fill="none"
|
||||
stroke="var(--center-channel-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-opacity="0.8"
|
||||
/>
|
||||
<path
|
||||
d="M43.255 50H60.255"
|
||||
fill="none"
|
||||
stroke="var(--center-channel-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-opacity="0.8"
|
||||
/>
|
||||
<path
|
||||
d="M65.255 50H80.255"
|
||||
fill="none"
|
||||
stroke="var(--center-channel-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-opacity="0.8"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
<div
|
||||
class="no-results__text-container"
|
||||
>
|
||||
<h3
|
||||
class="no-results__title thread-no-results-title"
|
||||
>
|
||||
That’s the end of the list
|
||||
</h3>
|
||||
<div
|
||||
class="no-results__subtitle thread-no-results-subtitle"
|
||||
>
|
||||
If you’re looking for older conversations, try searching with
|
||||
<span>
|
||||
Ctrl+F
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
+26
-24
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
@@ -9,15 +8,11 @@ import {getThreadsForCurrentTeam} from 'mattermost-redux/actions/threads';
|
||||
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
import Header from 'components/widgets/header';
|
||||
|
||||
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
|
||||
import {WindowSizes} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import ThreadList, {ThreadFilter} from './thread_list';
|
||||
import VirtualizedThreadList from './virtualized_thread_list';
|
||||
|
||||
import Button from '../../common/button';
|
||||
|
||||
jest.mock('mattermost-redux/actions/threads');
|
||||
jest.mock('actions/views/modals');
|
||||
@@ -27,6 +22,7 @@ const mockRouting = {
|
||||
currentTeamId: 'tid',
|
||||
goToInChannel: jest.fn(),
|
||||
select: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
};
|
||||
jest.mock('../../hooks', () => {
|
||||
return {
|
||||
@@ -34,6 +30,14 @@ jest.mock('../../hooks', () => {
|
||||
};
|
||||
});
|
||||
|
||||
let capturedVTLProps: any = {};
|
||||
jest.mock('./virtualized_thread_list', () => {
|
||||
return function MockVirtualizedThreadList(props: any) {
|
||||
capturedVTLProps = props;
|
||||
return <div data-testid='virtualized-thread-list'/>;
|
||||
};
|
||||
});
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
let mockState: any;
|
||||
|
||||
@@ -87,56 +91,54 @@ describe('components/threading/global_threads/thread_list', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
capturedVTLProps = {};
|
||||
mockDispatch.mockClear();
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const {baseElement} = renderWithContext(
|
||||
<ThreadList {...props}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should support filter:all', () => {
|
||||
const wrapper = shallow(
|
||||
test('should support filter:all', async () => {
|
||||
renderWithContext(
|
||||
<ThreadList {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.find(Header).shallow().find(Button).first().shallow().simulate('click');
|
||||
await userEvent.click(screen.getByRole('tab', {name: 'Followed threads'}));
|
||||
expect(props.setFilter).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
test('should support filter:unread', () => {
|
||||
const wrapper = shallow(
|
||||
test('should support filter:unread', async () => {
|
||||
renderWithContext(
|
||||
<ThreadList {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.find(Header).shallow().find(Button).find({hasDot: true}).simulate('click');
|
||||
await userEvent.click(screen.getByRole('tab', {name: 'Unreads'}));
|
||||
expect(props.setFilter).toHaveBeenCalledWith('unread');
|
||||
});
|
||||
|
||||
test('should support openModal', () => {
|
||||
const wrapper = shallow(
|
||||
test('should support openModal', async () => {
|
||||
renderWithContext(
|
||||
<ThreadList {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.find(Header).shallow().find(Button).find({id: 'threads-list__mark-all-as-read'}).simulate('click');
|
||||
await userEvent.click(screen.getByLabelText('Mark all threads as read'));
|
||||
expect(openModal).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should support getThreads', async () => {
|
||||
const setState = jest.fn();
|
||||
const useStateSpy = jest.spyOn(React, 'useState');
|
||||
useStateSpy.mockImplementation((init = false) => [init, setState]);
|
||||
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<ThreadList {...props}/>,
|
||||
);
|
||||
|
||||
const handleLoadMoreItems = wrapper.find(VirtualizedThreadList).prop('loadMoreItems');
|
||||
const handleLoadMoreItems = capturedVTLProps.loadMoreItems;
|
||||
const loadMoreItems = await handleLoadMoreItems(2, 3);
|
||||
|
||||
expect(loadMoreItems).toEqual({data: true});
|
||||
expect(getThreadsForCurrentTeam).toHaveBeenCalledWith({unread: false, before: '2'});
|
||||
expect(setState.mock.calls).toEqual([[true], [false], [true]]);
|
||||
});
|
||||
});
|
||||
|
||||
+23
-4
@@ -1,12 +1,32 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import VirtualizedThreadList from './virtualized_thread_list';
|
||||
|
||||
jest.mock('react-virtualized-auto-sizer', () => {
|
||||
return function MockAutoSizer({children}: {children: (size: {height: number; width: number}) => React.ReactNode}) {
|
||||
return <div>{children({height: 500, width: 300})}</div>;
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('./virtualized_thread_list_row', () => {
|
||||
return function MockRow({index, style, data}: any) {
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
data-testid={`thread-row-${data.ids[index]}`}
|
||||
>
|
||||
{data.ids[index]}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
describe('components/threading/global_threads/thread_list/virtualized_thread_list', () => {
|
||||
let props: ComponentProps<typeof VirtualizedThreadList>;
|
||||
let loadMoreItems: (startIndex: number, stopIndex: number) => Promise<any>;
|
||||
@@ -25,8 +45,7 @@ describe('components/threading/global_threads/thread_list/virtualized_thread_lis
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(<VirtualizedThreadList {...props}/>);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
const {baseElement} = renderWithContext(<VirtualizedThreadList {...props}/>);
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
+27
-12
@@ -1,14 +1,30 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {Constants} from 'utils/constants';
|
||||
|
||||
import Row from './virtualized_thread_list_row';
|
||||
|
||||
jest.mock('../thread_item', () => {
|
||||
return function MockThreadItem(props: any) {
|
||||
return (
|
||||
<div
|
||||
data-testid={`thread-item-${props.threadId}`}
|
||||
data-selected={props.isSelected}
|
||||
style={props.style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('components/search_shortcut/search_shortcut', () => ({
|
||||
SearchShortcut: () => <span>{'Ctrl+F'}</span>,
|
||||
}));
|
||||
|
||||
describe('components/threading/global_threads/thread_list/virtualized_thread_list_row', () => {
|
||||
let props: ComponentProps<typeof Row>;
|
||||
|
||||
@@ -24,30 +40,29 @@ describe('components/threading/global_threads/thread_list/virtualized_thread_lis
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(<Row {...props}/>);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
const {baseElement} = renderWithContext(<Row {...props}/>);
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should support item loading indicator', () => {
|
||||
const wrapper = shallow(
|
||||
const {baseElement} = renderWithContext(
|
||||
<Row
|
||||
{...props}
|
||||
data={{ids: [...props.data.ids, Constants.THREADS_LOADING_INDICATOR_ITEM_ID], selectedThreadId: undefined}}
|
||||
index={3}
|
||||
/>);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
/>,
|
||||
);
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should support item search guidance ', () => {
|
||||
const wrapper = shallow(
|
||||
const {baseElement} = renderWithContext(
|
||||
<Row
|
||||
{...props}
|
||||
data={{ids: [...props.data.ids, Constants.THREADS_NO_RESULTS_ITEM_ID], selectedThreadId: undefined}}
|
||||
index={3}
|
||||
/>);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
/>,
|
||||
);
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
+51
-43
@@ -1,56 +1,64 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/threading/global_threads/thread_pane should match snapshot 1`] = `
|
||||
<div
|
||||
className="ThreadPane"
|
||||
id="thread-pane-container"
|
||||
>
|
||||
<Header
|
||||
className="ThreadPane___header"
|
||||
heading={
|
||||
<React.Fragment>
|
||||
<Memo(Button)
|
||||
className="Button___icon Button___large back"
|
||||
onClick={[Function]}
|
||||
<div>
|
||||
<div
|
||||
class="ThreadPane"
|
||||
id="thread-pane-container"
|
||||
>
|
||||
<div
|
||||
class="Header ThreadPane___header"
|
||||
>
|
||||
<div
|
||||
class="left"
|
||||
>
|
||||
<button
|
||||
class="Button Button___transparent Button___icon Button___large back"
|
||||
>
|
||||
<i
|
||||
className="icon icon-arrow-back-ios"
|
||||
/>
|
||||
</Memo(Button)>
|
||||
<span
|
||||
class="Button_label"
|
||||
>
|
||||
<i
|
||||
class="icon icon-arrow-back-ios"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
<h3>
|
||||
<span
|
||||
className="separated"
|
||||
class="separated"
|
||||
>
|
||||
Thread
|
||||
</span>
|
||||
<Memo(Button)
|
||||
allowTextOverflow={true}
|
||||
className="separated"
|
||||
onClick={[Function]}
|
||||
<button
|
||||
class="Button Button___transparent allowTextOverflow separated"
|
||||
>
|
||||
Team name
|
||||
</Memo(Button)>
|
||||
<span
|
||||
class="Button_label"
|
||||
>
|
||||
Team name
|
||||
</span>
|
||||
</button>
|
||||
</h3>
|
||||
</React.Fragment>
|
||||
}
|
||||
right={
|
||||
<React.Fragment>
|
||||
<Memo(FollowButton)
|
||||
isFollowing={true}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<PopoutButton
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<Memo(ThreadMenu)
|
||||
hasUnreads={false}
|
||||
idPrefix="thread-menu"
|
||||
isFollowing={true}
|
||||
threadId="1y8hpek81byspd4enyk9mp1ncw"
|
||||
unreadTimestamp={1611786714912}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button Button___transparent is-active FollowButton"
|
||||
>
|
||||
<span
|
||||
class="Button_label"
|
||||
>
|
||||
Following
|
||||
</span>
|
||||
</button>
|
||||
<button>
|
||||
Popout
|
||||
</button>
|
||||
<div
|
||||
data-testid="thread-menu"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
+41
-29
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
@@ -9,15 +8,15 @@ import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {setThreadFollow} from 'mattermost-redux/actions/threads';
|
||||
|
||||
import Button from 'components/threading/common/button';
|
||||
import FollowButton from 'components/threading/common/follow_button';
|
||||
import Header from 'components/widgets/header';
|
||||
|
||||
import TestHelper from 'packages/mattermost-redux/test/test_helper';
|
||||
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
|
||||
|
||||
import ThreadPane from './thread_pane';
|
||||
|
||||
jest.mock('mattermost-redux/actions/threads');
|
||||
jest.mock('mattermost-redux/actions/threads', () => ({
|
||||
...jest.requireActual('mattermost-redux/actions/threads'),
|
||||
setThreadFollow: jest.fn(() => ({type: 'MOCK_SET_THREAD_FOLLOW'})),
|
||||
}));
|
||||
|
||||
const mockRouting = {
|
||||
params: {
|
||||
@@ -34,20 +33,28 @@ jest.mock('../../hooks', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
let mockState: any;
|
||||
jest.mock('components/popout_button', () => ({
|
||||
__esModule: true,
|
||||
default: ({onClick}: {onClick: () => void}) => (
|
||||
<button onClick={onClick}>{'Popout'}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux') as typeof import('react-redux'),
|
||||
useSelector: (selector: (state: typeof mockState) => unknown) => selector(mockState),
|
||||
useDispatch: () => mockDispatch,
|
||||
jest.mock('../thread_menu', () => ({
|
||||
__esModule: true,
|
||||
default: ({children}: {children: React.ReactNode}) => (
|
||||
<div data-testid='thread-menu'>{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('components/threading/global_threads/thread_pane', () => {
|
||||
let props: ComponentProps<typeof ThreadPane>;
|
||||
let mockThread: typeof props['thread'];
|
||||
let initialState: any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockThread = {
|
||||
id: '1y8hpek81byspd4enyk9mp1ncw',
|
||||
unread_replies: 0,
|
||||
@@ -67,7 +74,7 @@ describe('components/threading/global_threads/thread_pane', () => {
|
||||
const profiles: Record<string, UserProfile> = {};
|
||||
profiles[user1.id] = user1;
|
||||
|
||||
mockState = {
|
||||
initialState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {},
|
||||
@@ -104,48 +111,53 @@ describe('components/threading/global_threads/thread_pane', () => {
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<ThreadPane {...props}/>,
|
||||
initialState,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should support follow', () => {
|
||||
test('should support follow', async () => {
|
||||
props.thread.is_following = false;
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<ThreadPane {...props}/>,
|
||||
initialState,
|
||||
);
|
||||
wrapper.find(Header).shallow().find(FollowButton).shallow().simulate('click');
|
||||
await userEvent.click(screen.getByText('Follow'));
|
||||
expect(setThreadFollow).toHaveBeenCalledWith(mockRouting.currentUserId, mockRouting.currentTeamId, mockThread.id, true);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should support unfollow', () => {
|
||||
test('should support unfollow', async () => {
|
||||
props.thread.is_following = true;
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<ThreadPane {...props}/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
wrapper.find(Header).shallow().find(FollowButton).shallow().simulate('click');
|
||||
await userEvent.click(screen.getByText('Following'));
|
||||
expect(setThreadFollow).toHaveBeenCalledWith(mockRouting.currentUserId, mockRouting.currentTeamId, mockThread.id, false);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should support openInChannel', () => {
|
||||
const wrapper = shallow(
|
||||
test('should support openInChannel', async () => {
|
||||
renderWithContext(
|
||||
<ThreadPane {...props}/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
wrapper.find(Header).shallow().find('h3').find(Button).simulate('click');
|
||||
await userEvent.click(screen.getByText('Team name'));
|
||||
expect(mockRouting.goToInChannel).toHaveBeenCalledWith('1y8hpek81byspd4enyk9mp1ncw');
|
||||
});
|
||||
|
||||
test('should support go back to list', () => {
|
||||
const wrapper = shallow(
|
||||
test('should support go back to list', async () => {
|
||||
renderWithContext(
|
||||
<ThreadPane {...props}/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
wrapper.find(Header).shallow().find(Button).find('.back').simulate('click');
|
||||
const backButton = document.querySelector('.back') as HTMLElement;
|
||||
expect(backButton).toBeInTheDocument();
|
||||
await userEvent.click(backButton);
|
||||
expect(mockRouting.select).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
+8
-46
@@ -1,58 +1,20 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/threading/ThreadViewer should match snapshot 1`] = `
|
||||
<Fragment>
|
||||
<div>
|
||||
<div
|
||||
className="ThreadViewer"
|
||||
class="ThreadViewer"
|
||||
>
|
||||
<div
|
||||
className="post-right-comments-container"
|
||||
class="post-right-comments-container"
|
||||
>
|
||||
<FileUploadOverlay
|
||||
id="threadView"
|
||||
overlayType="right"
|
||||
<div
|
||||
data-testid="file-upload-overlay"
|
||||
/>
|
||||
<DeferredRenderWrapper
|
||||
channelId="channel_id"
|
||||
isThreadView={false}
|
||||
key="id"
|
||||
onCardClick={[Function]}
|
||||
postIds={
|
||||
Array [
|
||||
"id",
|
||||
]
|
||||
}
|
||||
selected={
|
||||
Object {
|
||||
"channel_id": "channel_id",
|
||||
"create_at": 1502715365009,
|
||||
"delete_at": 0,
|
||||
"edit_at": 0,
|
||||
"hashtags": "",
|
||||
"id": "id",
|
||||
"is_following": true,
|
||||
"is_pinned": false,
|
||||
"message": "post message",
|
||||
"metadata": Object {
|
||||
"embeds": Array [],
|
||||
"emojis": Array [],
|
||||
"files": Array [],
|
||||
"images": Object {},
|
||||
"reactions": Array [],
|
||||
},
|
||||
"original_id": "",
|
||||
"pending_post_id": "",
|
||||
"props": Object {},
|
||||
"reply_count": 3,
|
||||
"root_id": "",
|
||||
"type": "system_add_remove",
|
||||
"update_at": 1502715372443,
|
||||
"user_id": "user_id",
|
||||
}
|
||||
}
|
||||
useRelativeTimestamp={false}
|
||||
<div
|
||||
data-testid="virtualized-thread-viewer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import {render, waitFor} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
@@ -16,6 +16,23 @@ import type {FakePost} from 'types/store/rhs';
|
||||
import ThreadViewer from './thread_viewer';
|
||||
import type {Props} from './thread_viewer';
|
||||
|
||||
jest.mock('components/deferComponentRender', () => (component: any) => component);
|
||||
|
||||
jest.mock('../virtualized_thread_viewer', () => () => (
|
||||
<div data-testid='virtualized-thread-viewer'/>
|
||||
));
|
||||
|
||||
jest.mock('components/file_upload_overlay', () => () => (
|
||||
<div data-testid='file-upload-overlay'/>
|
||||
));
|
||||
|
||||
jest.mock('client/web_websocket_client', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
updateActiveThread: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('components/threading/ThreadViewer', () => {
|
||||
const post: Post = TestHelper.getPostMock({
|
||||
channel_id: 'channel_id',
|
||||
@@ -91,24 +108,46 @@ describe('components/threading/ThreadViewer', () => {
|
||||
test('should match snapshot', async () => {
|
||||
const reset = fakeDate(new Date(1502715365000));
|
||||
|
||||
const wrapper = shallow(
|
||||
const {container} = render(
|
||||
<ThreadViewer {...baseProps}/>,
|
||||
);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
await waitFor(() => {
|
||||
expect(actions.getPostThread).toHaveBeenCalled();
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
reset();
|
||||
});
|
||||
|
||||
test('should make api call to get thread posts on socket reconnect', () => {
|
||||
const wrapper = shallow(
|
||||
test('should make api call to get thread posts on socket reconnect', async () => {
|
||||
const {rerender} = render(
|
||||
<ThreadViewer {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.setProps({socketConnectionStatus: false});
|
||||
wrapper.setProps({socketConnectionStatus: true});
|
||||
// ThreadViewer calls getPostThread(rootId, !reconnected, lastUpdateAt) from onInit(reconnected).
|
||||
// Initial mount: onInit(false) → fetchThreads true. After reconnect: onInit(true) → fetchThreads false.
|
||||
await waitFor(() => {
|
||||
expect(actions.getPostThread).toHaveBeenCalledWith(post.id, true, 1234);
|
||||
});
|
||||
actions.getPostThread.mockClear();
|
||||
|
||||
return expect(actions.getPostThread).toHaveBeenCalledWith(post.id, true, 1234);
|
||||
rerender(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
socketConnectionStatus={false}
|
||||
/>,
|
||||
);
|
||||
rerender(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
socketConnectionStatus={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(actions.getPostThread).toHaveBeenCalledTimes(1);
|
||||
expect(actions.getPostThread).toHaveBeenCalledWith(post.id, false, 1234);
|
||||
});
|
||||
});
|
||||
|
||||
test('should not break if root post is a fake post', () => {
|
||||
@@ -118,7 +157,7 @@ describe('components/threading/ThreadViewer', () => {
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
shallow(<ThreadViewer {...props}/>);
|
||||
render(<ThreadViewer {...props}/>);
|
||||
}).not.toThrow("Cannot read property 'reply_count' of undefined");
|
||||
});
|
||||
|
||||
@@ -130,28 +169,25 @@ describe('components/threading/ThreadViewer', () => {
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
shallow(<ThreadViewer {...props}/>);
|
||||
render(<ThreadViewer {...props}/>);
|
||||
}).not.toThrow("Cannot read property 'reply_count' of undefined");
|
||||
});
|
||||
|
||||
test('should call fetchThread when no thread on mount', (done) => {
|
||||
test('should call fetchThread when no thread on mount', async () => {
|
||||
const {actions} = baseProps;
|
||||
|
||||
shallow(
|
||||
render(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
isCollapsedThreadsEnabled={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect.assertions(3);
|
||||
|
||||
process.nextTick(() => {
|
||||
expect(actions.updateThreadLastOpened).not.toHaveBeenCalled();
|
||||
expect(actions.updateThreadRead).not.toHaveBeenCalled();
|
||||
await waitFor(() => {
|
||||
expect(actions.getThread).toHaveBeenCalledWith('user_id', 'team_id', 'id', true);
|
||||
done();
|
||||
});
|
||||
expect(actions.updateThreadLastOpened).not.toHaveBeenCalled();
|
||||
expect(actions.updateThreadRead).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call updateThreadLastOpened on mount', () => {
|
||||
@@ -163,7 +199,7 @@ describe('components/threading/ThreadViewer', () => {
|
||||
last_reply_at: 32,
|
||||
} as UserThread;
|
||||
|
||||
shallow(
|
||||
render(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
userThread={userThread}
|
||||
@@ -171,7 +207,6 @@ describe('components/threading/ThreadViewer', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect.assertions(3);
|
||||
expect(actions.updateThreadLastOpened).toHaveBeenCalledWith('id', 42);
|
||||
expect(actions.updateThreadRead).not.toHaveBeenCalled();
|
||||
expect(actions.getThread).not.toHaveBeenCalled();
|
||||
@@ -186,7 +221,7 @@ describe('components/threading/ThreadViewer', () => {
|
||||
last_reply_at: 142,
|
||||
} as UserThread;
|
||||
|
||||
shallow(
|
||||
render(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
userThread={userThread}
|
||||
@@ -194,13 +229,12 @@ describe('components/threading/ThreadViewer', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect.assertions(3);
|
||||
expect(actions.updateThreadLastOpened).toHaveBeenCalledWith('id', 42);
|
||||
expect(actions.updateThreadRead).toHaveBeenCalledWith('user_id', 'team_id', 'id', 400);
|
||||
expect(actions.getThread).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call updateThreadLastOpened and updateThreadRead upon thread id change', (done) => {
|
||||
test('should call updateThreadLastOpened and updateThreadRead upon thread id change', async () => {
|
||||
jest.useRealTimers();
|
||||
const dateNowOrig = Date.now;
|
||||
Date.now = () => new Date(400).getMilliseconds();
|
||||
@@ -212,34 +246,48 @@ describe('components/threading/ThreadViewer', () => {
|
||||
last_reply_at: 142,
|
||||
} as UserThread;
|
||||
|
||||
const wrapper = shallow(
|
||||
const {rerender} = render(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
isCollapsedThreadsEnabled={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect.assertions(6);
|
||||
process.nextTick(() => {
|
||||
expect(actions.updateThreadLastOpened).not.toHaveBeenCalled();
|
||||
expect(actions.updateThreadRead).not.toHaveBeenCalled();
|
||||
await waitFor(() => {
|
||||
expect(actions.getThread).toHaveBeenCalled();
|
||||
|
||||
jest.resetAllMocks();
|
||||
wrapper.setProps({userThread});
|
||||
|
||||
expect(actions.updateThreadLastOpened).toHaveBeenCalledWith('id', 42);
|
||||
expect(actions.updateThreadRead).toHaveBeenCalledWith('user_id', 'team_id', 'id', 400);
|
||||
expect(actions.getThread).not.toHaveBeenCalled();
|
||||
Date.now = dateNowOrig;
|
||||
done();
|
||||
});
|
||||
expect(actions.updateThreadLastOpened).not.toHaveBeenCalled();
|
||||
expect(actions.updateThreadRead).not.toHaveBeenCalled();
|
||||
|
||||
jest.resetAllMocks();
|
||||
jest.mocked(baseProps.actions.getPostThread).mockResolvedValue({
|
||||
data: {
|
||||
order: ['post1', 'post2', 'post3'],
|
||||
posts: {
|
||||
post1: TestHelper.getPostMock({id: 'post1', update_at: 1000}),
|
||||
post2: TestHelper.getPostMock({id: 'post2', update_at: 2000}),
|
||||
post3: TestHelper.getPostMock({id: 'post3', update_at: 1500}),
|
||||
},
|
||||
},
|
||||
});
|
||||
rerender(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
isCollapsedThreadsEnabled={true}
|
||||
userThread={userThread}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(actions.updateThreadLastOpened).toHaveBeenCalledWith('id', 42);
|
||||
expect(actions.updateThreadRead).toHaveBeenCalledWith('user_id', 'team_id', 'id', 400);
|
||||
expect(actions.getThread).not.toHaveBeenCalled();
|
||||
Date.now = dateNowOrig;
|
||||
});
|
||||
|
||||
test('should call fetchRHSAppsBindings on mount if appsEnabled', () => {
|
||||
const {actions} = baseProps;
|
||||
|
||||
shallow(
|
||||
render(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
/>,
|
||||
@@ -251,7 +299,7 @@ describe('components/threading/ThreadViewer', () => {
|
||||
test('should not call fetchRHSAppsBindings on mount if not appsEnabled', () => {
|
||||
const {actions} = baseProps;
|
||||
|
||||
shallow(
|
||||
render(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
appsEnabled={false}
|
||||
@@ -264,17 +312,17 @@ describe('components/threading/ThreadViewer', () => {
|
||||
test('should update thread with highest update_at value when lastUpdateAt is 0', async () => {
|
||||
const {actions} = baseProps;
|
||||
|
||||
shallow(
|
||||
render(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
lastUpdateAt={0} // Set lastUpdateAt to 0
|
||||
/>,
|
||||
);
|
||||
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
// Verify getPostThread was called with lastUpdateAt = 0
|
||||
expect(actions.getPostThread).toHaveBeenCalledWith(post.id, true, 0);
|
||||
await waitFor(() => {
|
||||
// Verify getPostThread was called with lastUpdateAt = 0
|
||||
expect(actions.getPostThread).toHaveBeenCalledWith(post.id, true, 0);
|
||||
});
|
||||
|
||||
// Verify updateThreadLastUpdateAt was called with the highest update_at value
|
||||
expect(actions.updateThreadLastUpdateAt).toHaveBeenCalledWith(post.id, 2000);
|
||||
@@ -293,15 +341,15 @@ describe('components/threading/ThreadViewer', () => {
|
||||
},
|
||||
});
|
||||
|
||||
shallow(
|
||||
render(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
// Verify updateThreadLastUpdateAt was called with the highest update_at value
|
||||
expect(actions.updateThreadLastUpdateAt).toHaveBeenCalledWith(post.id, 9000);
|
||||
await waitFor(() => {
|
||||
// Verify updateThreadLastUpdateAt was called with the highest update_at value
|
||||
expect(actions.updateThreadLastUpdateAt).toHaveBeenCalledWith(post.id, 9000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+76
-56
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import type {ComponentProps} from 'react';
|
||||
import React from 'react';
|
||||
|
||||
@@ -11,10 +10,30 @@ import type {DeepPartial} from '@mattermost/types/utilities';
|
||||
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import VirtualizedThreadViewer from './virtualized_thread_viewer';
|
||||
|
||||
const mockScrollToItem = jest.fn();
|
||||
|
||||
jest.mock('components/dynamic_virtualized_list', () => {
|
||||
const ReactMock = require('react');
|
||||
return {
|
||||
DynamicVirtualizedList: ReactMock.forwardRef((props: any, ref: any) => {
|
||||
ReactMock.useImperativeHandle(ref, () => ({
|
||||
scrollToItem: mockScrollToItem,
|
||||
}));
|
||||
return <div data-testid='virtualized-list'/>;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('./create_comment', () => () => <div data-testid='create-comment'/>);
|
||||
jest.mock('./thread_viewer_row', () => () => <div data-testid='thread-viewer-row'/>);
|
||||
jest.mock('components/new_replies_banner', () => () => null);
|
||||
jest.mock('components/post_view/floating_timestamp', () => () => null);
|
||||
|
||||
type Props = ComponentProps<typeof VirtualizedThreadViewer>;
|
||||
function getBasePropsAndState(): [Props, DeepPartial<GlobalState>] {
|
||||
const channel = TestHelper.getChannelMock();
|
||||
@@ -73,75 +92,76 @@ function getBasePropsAndState(): [Props, DeepPartial<GlobalState>] {
|
||||
}
|
||||
|
||||
describe('components/threading/VirtualizedThreadViewer', () => {
|
||||
const [baseProps] = getBasePropsAndState();
|
||||
const [baseProps, baseState] = getBasePropsAndState();
|
||||
|
||||
beforeEach(() => {
|
||||
mockScrollToItem.mockClear();
|
||||
});
|
||||
|
||||
test('should scroll to the bottom when the current user makes a new post in the thread', () => {
|
||||
const scrollToBottom = jest.fn();
|
||||
|
||||
const wrapper = shallow(
|
||||
const {rerender} = renderWithContext(
|
||||
<VirtualizedThreadViewer {...baseProps}/>,
|
||||
baseState,
|
||||
);
|
||||
const instance = wrapper.instance() as VirtualizedThreadViewer;
|
||||
instance.scrollToBottom = scrollToBottom;
|
||||
|
||||
expect(scrollToBottom).not.toHaveBeenCalled();
|
||||
wrapper.setProps({
|
||||
lastPost:
|
||||
{
|
||||
mockScrollToItem.mockClear();
|
||||
|
||||
rerender(
|
||||
<VirtualizedThreadViewer
|
||||
{...baseProps}
|
||||
lastPost={TestHelper.getPostMock({
|
||||
id: 'newpost',
|
||||
root_id: baseProps.selected.id,
|
||||
user_id: 'user_id',
|
||||
},
|
||||
});
|
||||
|
||||
expect(scrollToBottom).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not scroll to the bottom when another user makes a new post in the thread', () => {
|
||||
const scrollToBottom = jest.fn();
|
||||
|
||||
const wrapper = shallow(
|
||||
<VirtualizedThreadViewer {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance() as VirtualizedThreadViewer;
|
||||
instance.scrollToBottom = scrollToBottom;
|
||||
|
||||
expect(scrollToBottom).not.toHaveBeenCalled();
|
||||
|
||||
wrapper.setProps({
|
||||
lastPost:
|
||||
{
|
||||
id: 'newpost',
|
||||
root_id: baseProps.selected.id,
|
||||
user_id: 'other_user_id',
|
||||
},
|
||||
});
|
||||
|
||||
expect(scrollToBottom).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not scroll to the bottom when there is a highlighted reply', () => {
|
||||
const scrollToBottom = jest.fn();
|
||||
|
||||
const wrapper = shallow(
|
||||
<VirtualizedThreadViewer
|
||||
{...baseProps}
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
const instance = wrapper.instance() as VirtualizedThreadViewer;
|
||||
instance.scrollToBottom = scrollToBottom;
|
||||
expect(mockScrollToItem).toHaveBeenCalledWith(0, 'end', undefined);
|
||||
});
|
||||
|
||||
wrapper.setProps({
|
||||
lastPost:
|
||||
{
|
||||
test('should not scroll to the bottom when another user makes a new post in the thread', () => {
|
||||
const {rerender} = renderWithContext(
|
||||
<VirtualizedThreadViewer {...baseProps}/>,
|
||||
baseState,
|
||||
);
|
||||
|
||||
mockScrollToItem.mockClear();
|
||||
|
||||
rerender(
|
||||
<VirtualizedThreadViewer
|
||||
{...baseProps}
|
||||
lastPost={TestHelper.getPostMock({
|
||||
id: 'newpost',
|
||||
root_id: baseProps.selected.id,
|
||||
user_id: 'other_user_id',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockScrollToItem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not scroll to the bottom when there is a highlighted reply', () => {
|
||||
const {rerender} = renderWithContext(
|
||||
<VirtualizedThreadViewer {...baseProps}/>,
|
||||
baseState,
|
||||
);
|
||||
|
||||
mockScrollToItem.mockClear();
|
||||
|
||||
rerender(
|
||||
<VirtualizedThreadViewer
|
||||
{...baseProps}
|
||||
lastPost={TestHelper.getPostMock({
|
||||
id: 'newpost',
|
||||
root_id: baseProps.selected.id,
|
||||
user_id: 'user_id',
|
||||
},
|
||||
highlightedPostId: '42',
|
||||
});
|
||||
})}
|
||||
highlightedPostId='42'
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(scrollToBottom).not.toHaveBeenCalled();
|
||||
expect(mockScrollToItem).not.toHaveBeenCalledWith(0, 'end', undefined);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+110
-74
@@ -1,17 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ShallowWrapper} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
import type {ChannelType} from '@mattermost/types/channels';
|
||||
import type {TeamType} from '@mattermost/types/teams';
|
||||
|
||||
import UnreadsStatusHandler from 'components/unreads_status_handler/unreads_status_handler';
|
||||
import type {UnreadsStatusHandlerClass} from 'components/unreads_status_handler/unreads_status_handler';
|
||||
import UnreadsStatusHandler, {UnreadsStatusHandlerClass} from 'components/unreads_status_handler/unreads_status_handler';
|
||||
|
||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {Constants} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
import {isChrome, isFirefox} from 'utils/user_agent';
|
||||
@@ -51,70 +49,78 @@ describe('components/UnreadsStatusHandler', () => {
|
||||
};
|
||||
|
||||
test('set correctly the title when needed', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<UnreadsStatusHandler {...defaultProps}/>,
|
||||
) as unknown as ShallowWrapper<Props, any, UnreadsStatusHandlerClass>;
|
||||
const instance = wrapper.instance();
|
||||
instance.updateTitle();
|
||||
instance.componentDidUpdate = jest.fn();
|
||||
instance.render = jest.fn();
|
||||
// Render with slightly different prop to trigger componentDidUpdate on first rerender
|
||||
const {rerender} = renderWithContext(
|
||||
<UnreadsStatusHandler
|
||||
{...defaultProps}
|
||||
unreadStatus={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Track cumulative props like Enzyme's setProps
|
||||
let currentProps: any = {...defaultProps};
|
||||
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('Public test 1 - Test team display name Test site');
|
||||
|
||||
wrapper.setProps({
|
||||
siteName: undefined,
|
||||
});
|
||||
instance.updateTitle();
|
||||
currentProps = {...currentProps, siteName: undefined};
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('Public test 1 - Test team display name');
|
||||
|
||||
wrapper.setProps({
|
||||
currentProps = {
|
||||
...currentProps,
|
||||
currentChannel: {id: '1', type: Constants.DM_CHANNEL} as Props['currentChannel'],
|
||||
currentTeammate: {display_name: 'teammate'} as Props['currentTeammate'],
|
||||
});
|
||||
instance.updateTitle();
|
||||
};
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('teammate - Test team display name');
|
||||
|
||||
wrapper.setProps({
|
||||
unreadStatus: 3,
|
||||
});
|
||||
instance.updateTitle();
|
||||
currentProps = {...currentProps, unreadStatus: 3};
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('(3) teammate - Test team display name');
|
||||
|
||||
wrapper.setProps({
|
||||
currentProps = {
|
||||
...currentProps,
|
||||
currentChannel: {} as Props['currentChannel'],
|
||||
currentTeammate: {} as Props['currentTeammate']});
|
||||
instance.updateTitle();
|
||||
currentTeammate: {} as Props['currentTeammate'],
|
||||
};
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('Mattermost - Join a team');
|
||||
|
||||
wrapper.setProps({
|
||||
currentProps = {
|
||||
...currentProps,
|
||||
inDrafts: false,
|
||||
inScheduledPosts: true,
|
||||
unreadStatus: 0,
|
||||
});
|
||||
instance.updateTitle();
|
||||
};
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('Scheduled - Test team display name');
|
||||
|
||||
wrapper.setProps({
|
||||
currentProps = {
|
||||
...currentProps,
|
||||
inDrafts: false,
|
||||
inScheduledPosts: true,
|
||||
unreadStatus: 10,
|
||||
});
|
||||
instance.updateTitle();
|
||||
};
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('(10) Scheduled - Test team display name');
|
||||
|
||||
wrapper.setProps({
|
||||
currentProps = {
|
||||
...currentProps,
|
||||
inDrafts: true,
|
||||
inScheduledPosts: false,
|
||||
unreadStatus: 0,
|
||||
});
|
||||
instance.updateTitle();
|
||||
};
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('Drafts - Test team display name');
|
||||
|
||||
wrapper.setProps({
|
||||
currentProps = {
|
||||
...currentProps,
|
||||
inDrafts: true,
|
||||
inScheduledPosts: false,
|
||||
unreadStatus: 10,
|
||||
});
|
||||
instance.updateTitle();
|
||||
};
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('(10) Drafts - Test team display name');
|
||||
});
|
||||
|
||||
@@ -123,76 +129,106 @@ describe('components/UnreadsStatusHandler', () => {
|
||||
// supported, hence we need to show * in title on mentions
|
||||
(isFirefox as jest.Mock).mockImplementation(() => false);
|
||||
(isChrome as jest.Mock).mockImplementation(() => false);
|
||||
const wrapper = shallowWithIntl(
|
||||
<UnreadsStatusHandler {...defaultProps}/>,
|
||||
) as unknown as ShallowWrapper<Props, any, UnreadsStatusHandlerClass>;
|
||||
const instance = wrapper.instance();
|
||||
|
||||
wrapper.setProps({
|
||||
siteName: undefined,
|
||||
});
|
||||
wrapper.setProps({
|
||||
const {rerender} = renderWithContext(
|
||||
<UnreadsStatusHandler {...defaultProps}/>,
|
||||
);
|
||||
|
||||
let currentProps: any = {...defaultProps, siteName: undefined};
|
||||
currentProps = {
|
||||
...currentProps,
|
||||
currentChannel: {id: '1', type: Constants.DM_CHANNEL} as Props['currentChannel'],
|
||||
currentTeammate: {display_name: 'teammate'} as Props['currentTeammate'],
|
||||
});
|
||||
wrapper.setProps({
|
||||
unreadStatus: 3,
|
||||
});
|
||||
instance.updateTitle();
|
||||
};
|
||||
currentProps = {...currentProps, unreadStatus: 3};
|
||||
rerender(<UnreadsStatusHandler {...currentProps}/>);
|
||||
expect(document.title).toBe('(3) * teammate - Test team display name');
|
||||
});
|
||||
|
||||
test('should display correct favicon', () => {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
document.head.appendChild(link);
|
||||
const sizes = ['16x16', '24x24', '32x32', '64x64', '96x96'];
|
||||
sizes.forEach((size) => {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
link.setAttribute('sizes', size);
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
|
||||
const wrapper = shallowWithIntl(
|
||||
(isFirefox as jest.Mock).mockReturnValue(true);
|
||||
|
||||
// Spy on getBadgeStatus to verify the correct badge status is computed
|
||||
// (updateFavicon is an arrow function so we can't spy on it directly)
|
||||
const getBadgeStatusSpy = jest.spyOn(UnreadsStatusHandlerClass.prototype, 'getBadgeStatus');
|
||||
|
||||
const {rerender} = renderWithContext(
|
||||
<UnreadsStatusHandler {...defaultProps}/>,
|
||||
) as unknown as ShallowWrapper<Props, any, UnreadsStatusHandlerClass>;
|
||||
const instance = wrapper.instance();
|
||||
instance.updateFavicon = jest.fn();
|
||||
);
|
||||
|
||||
wrapper.setProps({
|
||||
unreadStatus: 3,
|
||||
});
|
||||
expect(instance.updateFavicon).toHaveBeenLastCalledWith('Mention');
|
||||
rerender(
|
||||
<UnreadsStatusHandler
|
||||
{...defaultProps}
|
||||
unreadStatus={3}
|
||||
/>,
|
||||
);
|
||||
expect(getBadgeStatusSpy).toHaveLastReturnedWith('Mention');
|
||||
|
||||
wrapper.setProps({
|
||||
unreadStatus: true,
|
||||
});
|
||||
expect(instance.updateFavicon).toHaveBeenLastCalledWith('Unread');
|
||||
rerender(
|
||||
<UnreadsStatusHandler
|
||||
{...defaultProps}
|
||||
unreadStatus={true}
|
||||
/>,
|
||||
);
|
||||
expect(getBadgeStatusSpy).toHaveLastReturnedWith('Unread');
|
||||
|
||||
wrapper.setProps({
|
||||
unreadStatus: false,
|
||||
rerender(
|
||||
<UnreadsStatusHandler
|
||||
{...defaultProps}
|
||||
unreadStatus={false}
|
||||
/>,
|
||||
);
|
||||
expect(getBadgeStatusSpy).toHaveLastReturnedWith('None');
|
||||
|
||||
getBadgeStatusSpy.mockRestore();
|
||||
|
||||
// Clean up
|
||||
sizes.forEach((size) => {
|
||||
const link = document.querySelector(`link[rel="icon"][sizes="${size}"]`);
|
||||
if (link) {
|
||||
link.remove();
|
||||
}
|
||||
});
|
||||
expect(instance.updateFavicon).toHaveBeenLastCalledWith('None');
|
||||
});
|
||||
|
||||
test('should display correct title when in drafts', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
const {rerender} = renderWithContext(
|
||||
<UnreadsStatusHandler {...defaultProps}/>,
|
||||
);
|
||||
|
||||
rerender(
|
||||
<UnreadsStatusHandler
|
||||
{...defaultProps}
|
||||
inDrafts={true}
|
||||
currentChannel={undefined}
|
||||
siteName={undefined}
|
||||
/>,
|
||||
) as unknown as ShallowWrapper<Props, any, UnreadsStatusHandlerClass>;
|
||||
wrapper.instance().updateTitle();
|
||||
);
|
||||
|
||||
expect(document.title).toBe('Drafts - Test team display name');
|
||||
});
|
||||
|
||||
test('should display correct title when in scheduled posts tab', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
const {rerender} = renderWithContext(
|
||||
<UnreadsStatusHandler {...defaultProps}/>,
|
||||
);
|
||||
|
||||
rerender(
|
||||
<UnreadsStatusHandler
|
||||
{...defaultProps}
|
||||
inScheduledPosts={true}
|
||||
currentChannel={undefined}
|
||||
siteName={undefined}
|
||||
/>,
|
||||
) as unknown as ShallowWrapper<Props, any, UnreadsStatusHandlerClass>;
|
||||
wrapper.instance().updateTitle();
|
||||
);
|
||||
|
||||
expect(document.title).toBe('Scheduled - Test team display name');
|
||||
});
|
||||
|
||||
+23
-32
@@ -9,7 +9,7 @@ import type {UserProfile} from '@mattermost/types/users';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import {renderWithContext, act, userEvent} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, userEvent} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import GroupMemberList from './group_member_list';
|
||||
@@ -89,32 +89,26 @@ describe('component/user_group_popover/group_member_list', () => {
|
||||
},
|
||||
};
|
||||
|
||||
test('should match snapshot', async () => {
|
||||
let baseElement: HTMLElement;
|
||||
await act(async () => {
|
||||
({baseElement} = renderWithContext(
|
||||
<GroupMemberList
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
));
|
||||
});
|
||||
test('should match snapshot', () => {
|
||||
const {baseElement} = renderWithContext(
|
||||
<GroupMemberList
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
);
|
||||
|
||||
expect(baseElement!).toMatchSnapshot();
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should open dms', async () => {
|
||||
let container: HTMLElement;
|
||||
await act(async () => {
|
||||
({container} = renderWithContext(
|
||||
<GroupMemberList
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
));
|
||||
});
|
||||
const {container} = renderWithContext(
|
||||
<GroupMemberList
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
);
|
||||
|
||||
const dmButton = container!.querySelector('.group-member-list_dm-button');
|
||||
const dmButton = container.querySelector('.group-member-list_dm-button');
|
||||
if (dmButton) {
|
||||
await userEvent.click(dmButton);
|
||||
}
|
||||
@@ -122,17 +116,14 @@ describe('component/user_group_popover/group_member_list', () => {
|
||||
});
|
||||
|
||||
test('should show user overlay and hide', async () => {
|
||||
let container: HTMLElement;
|
||||
await act(async () => {
|
||||
({container} = renderWithContext(
|
||||
<GroupMemberList
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
));
|
||||
});
|
||||
const {container} = renderWithContext(
|
||||
<GroupMemberList
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
);
|
||||
|
||||
const listItem = container!.querySelector('.group-member-list_item');
|
||||
const listItem = container.querySelector('.group-member-list_item');
|
||||
if (listItem) {
|
||||
await userEvent.click(listItem);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import React from 'react';
|
||||
import type {Group} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {renderWithContext, act} from 'tests/react_testing_utils';
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import UserGroupPopover from './user_group_popover';
|
||||
@@ -84,44 +84,35 @@ describe('component/user_group_popover', () => {
|
||||
returnFocus: jest.fn(),
|
||||
};
|
||||
|
||||
test('should match snapshot', async () => {
|
||||
let baseElement: HTMLElement;
|
||||
await act(async () => {
|
||||
({baseElement} = renderWithContext(
|
||||
<UserGroupPopover
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
));
|
||||
});
|
||||
expect(baseElement!).toMatchSnapshot();
|
||||
test('should match snapshot', () => {
|
||||
const {baseElement} = renderWithContext(
|
||||
<UserGroupPopover
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
);
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should not show search bar', async () => {
|
||||
let container: HTMLElement;
|
||||
await act(async () => {
|
||||
({container} = renderWithContext(
|
||||
<UserGroupPopover
|
||||
{...baseProps}
|
||||
group={group2}
|
||||
/>,
|
||||
initialState as any,
|
||||
));
|
||||
});
|
||||
const {container} = renderWithContext(
|
||||
<UserGroupPopover
|
||||
{...baseProps}
|
||||
group={group2}
|
||||
/>,
|
||||
initialState as any,
|
||||
);
|
||||
|
||||
expect(container!.querySelector('.user-group-popover_search-bar')).not.toBeInTheDocument();
|
||||
expect(container.querySelector('.user-group-popover_search-bar')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show users', async () => {
|
||||
let container: HTMLElement;
|
||||
await act(async () => {
|
||||
({container} = renderWithContext(
|
||||
<UserGroupPopover
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
));
|
||||
});
|
||||
expect(container!.querySelectorAll('.group-member-list_item').length).toBeGreaterThan(0);
|
||||
const {container} = renderWithContext(
|
||||
<UserGroupPopover
|
||||
{...baseProps}
|
||||
/>,
|
||||
initialState as any,
|
||||
);
|
||||
expect(container.querySelectorAll('.group-member-list_item').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithContext, screen, act} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import {CloudProducts, LicenseSkus} from 'utils/constants';
|
||||
|
||||
import ADLDAPUpsellBanner from './ad_ldap_upsell_banner';
|
||||
@@ -81,12 +81,10 @@ describe('component/user_groups_modal/ad_ldap_upsell_banner', () => {
|
||||
},
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
renderWithContext(
|
||||
<ADLDAPUpsellBanner/>,
|
||||
state,
|
||||
);
|
||||
});
|
||||
renderWithContext(
|
||||
<ADLDAPUpsellBanner/>,
|
||||
state,
|
||||
);
|
||||
|
||||
expect(screen.getByText('AD/LDAP group sync creates groups faster')).toBeInTheDocument();
|
||||
expect(screen.getByText('Contact sales to use')).toBeInTheDocument();
|
||||
@@ -137,12 +135,10 @@ describe('component/user_groups_modal/ad_ldap_upsell_banner', () => {
|
||||
},
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
renderWithContext(
|
||||
<ADLDAPUpsellBanner/>,
|
||||
state,
|
||||
);
|
||||
});
|
||||
renderWithContext(
|
||||
<ADLDAPUpsellBanner/>,
|
||||
state,
|
||||
);
|
||||
|
||||
expect(screen.queryByText('AD/LDAP group sync creates groups faster')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
+22
-15
@@ -1,27 +1,34 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/MenuItemToggleModalRedux should match snapshot with extra text 1`] = `
|
||||
<Fragment>
|
||||
<ToggleModalButton
|
||||
className="MenuItem__with-help"
|
||||
dialogProps={
|
||||
Object {
|
||||
"test": "test",
|
||||
}
|
||||
}
|
||||
dialogType={[MockFunction]}
|
||||
modalId="test"
|
||||
exports[`components/MenuItemToggleModalRedux should match snapshot 1`] = `
|
||||
<div>
|
||||
<button
|
||||
class="style--none "
|
||||
>
|
||||
<span
|
||||
className="MenuItem__primary-text"
|
||||
class="MenuItem__primary-text"
|
||||
>
|
||||
Whatever
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/MenuItemToggleModalRedux should match snapshot with extra text 1`] = `
|
||||
<div>
|
||||
<button
|
||||
class="style--none MenuItem__with-help"
|
||||
>
|
||||
<span
|
||||
class="MenuItem__primary-text"
|
||||
>
|
||||
Whatever
|
||||
</span>
|
||||
<span
|
||||
className="MenuItem__help-text"
|
||||
class="MenuItem__help-text"
|
||||
>
|
||||
Extra text
|
||||
</span>
|
||||
</ToggleModalButton>
|
||||
</Fragment>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
+6
-24
@@ -1,14 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import {MenuItemToggleModalReduxImpl} from './menu_item_toggle_modal_redux';
|
||||
|
||||
describe('components/MenuItemToggleModalRedux', () => {
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<MenuItemToggleModalReduxImpl
|
||||
modalId='test'
|
||||
dialogType={jest.fn()}
|
||||
@@ -17,30 +18,11 @@ describe('components/MenuItemToggleModalRedux', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchInlineSnapshot(`
|
||||
<Fragment>
|
||||
<ToggleModalButton
|
||||
className=""
|
||||
dialogProps={
|
||||
Object {
|
||||
"test": "test",
|
||||
}
|
||||
}
|
||||
dialogType={[MockFunction]}
|
||||
modalId="test"
|
||||
>
|
||||
<span
|
||||
className="MenuItem__primary-text"
|
||||
>
|
||||
Whatever
|
||||
</span>
|
||||
</ToggleModalButton>
|
||||
</Fragment>
|
||||
`);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with extra text', () => {
|
||||
const wrapper = shallow(
|
||||
const {container} = renderWithContext(
|
||||
<MenuItemToggleModalReduxImpl
|
||||
modalId='test'
|
||||
dialogType={jest.fn()}
|
||||
@@ -50,6 +32,6 @@ describe('components/MenuItemToggleModalRedux', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
+150
-68
@@ -1,76 +1,158 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/submenu_modal should match snapshot 1`] = `
|
||||
<Modal
|
||||
animation={true}
|
||||
autoFocus={true}
|
||||
backdrop={true}
|
||||
bsClass="modal"
|
||||
dialogClassName="SubMenuModal a11y__modal mobile-sub-menu"
|
||||
dialogComponentClass={[Function]}
|
||||
enforceFocus={false}
|
||||
id="submenuModal"
|
||||
keyboard={true}
|
||||
manager={
|
||||
ModalManager {
|
||||
"add": [Function],
|
||||
"containers": Array [],
|
||||
"data": Array [],
|
||||
"handleContainerOverflow": true,
|
||||
"hideSiblingNodes": true,
|
||||
"isTopModal": [Function],
|
||||
"modals": Array [],
|
||||
"remove": [Function],
|
||||
}
|
||||
}
|
||||
onExited={[MockFunction]}
|
||||
onHide={[Function]}
|
||||
renderBackdrop={[Function]}
|
||||
restoreFocus={true}
|
||||
role="none"
|
||||
show={true}
|
||||
<body
|
||||
class="modal-open"
|
||||
style="padding-right: 0px;"
|
||||
>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
componentClass="div"
|
||||
data-testid="SubMenuModalBody"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
role="none"
|
||||
>
|
||||
<MenuWrapper
|
||||
animationComponent={[Function]}
|
||||
className=""
|
||||
<div
|
||||
class="fade modal-backdrop in"
|
||||
/>
|
||||
<div
|
||||
class="fade in modal"
|
||||
id="submenuModal"
|
||||
role="dialog"
|
||||
style="display: block; padding-right: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<Menu
|
||||
ariaLabel="mobile submenu"
|
||||
openLeft={true}
|
||||
<div
|
||||
class="SubMenuModal a11y__modal mobile-sub-menu modal-dialog"
|
||||
>
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[MockFunction]}
|
||||
id="A"
|
||||
key="A"
|
||||
root={false}
|
||||
text="Text A"
|
||||
/>
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[MockFunction]}
|
||||
id="B"
|
||||
key="B"
|
||||
root={false}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
"action": [MockFunction],
|
||||
"direction": "left",
|
||||
"id": "C",
|
||||
"text": "Text C",
|
||||
},
|
||||
]
|
||||
}
|
||||
text="Text B"
|
||||
/>
|
||||
</Menu>
|
||||
<div />
|
||||
</MenuWrapper>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
<div
|
||||
class="modal-content"
|
||||
role="document"
|
||||
>
|
||||
<div
|
||||
class="modal-body"
|
||||
data-testid="SubMenuModalBody"
|
||||
>
|
||||
<div
|
||||
class="MenuWrapper "
|
||||
>
|
||||
<div
|
||||
aria-label="mobile submenu"
|
||||
class="a11y__popup Menu"
|
||||
>
|
||||
<ul
|
||||
class="Menu__content dropdown-menu openLeft"
|
||||
role="menu"
|
||||
>
|
||||
<li
|
||||
class="SubMenuItem MenuItem"
|
||||
id="A_menuitem"
|
||||
role="menuitem"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
id="A"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="flex"
|
||||
>
|
||||
Text A
|
||||
<span
|
||||
class="selected"
|
||||
/>
|
||||
<span
|
||||
aria-label="submenu icon"
|
||||
class="fa fa-angle-right SubMenu__icon-right-empty"
|
||||
id="channelHeaderDropdownIconRight_A"
|
||||
/>
|
||||
</div>
|
||||
<ul
|
||||
class="a11y__popup Menu dropdown-menu SubMenu"
|
||||
id="A_submenu"
|
||||
style="visibility: hidden; top: unset; right: 100%;"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
aria-controls="B_submenu"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
class="SubMenuItem MenuItem"
|
||||
id="B_menuitem"
|
||||
role="menuitem"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
id="B"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="flex"
|
||||
>
|
||||
Text B
|
||||
<span
|
||||
class="selected"
|
||||
/>
|
||||
<span
|
||||
aria-label="submenu icon"
|
||||
class="fa fa-angle-right SubMenu__icon-right"
|
||||
id="channelHeaderDropdownIconRight_B"
|
||||
/>
|
||||
</div>
|
||||
<ul
|
||||
class="a11y__popup Menu dropdown-menu SubMenu"
|
||||
id="B_submenu"
|
||||
style="visibility: hidden; top: unset; right: 100%;"
|
||||
>
|
||||
<span
|
||||
class="SubMenuItemContainer"
|
||||
tabindex="0"
|
||||
>
|
||||
<li
|
||||
class="SubMenuItem MenuItem"
|
||||
id="C_menuitem"
|
||||
role="menuitem"
|
||||
>
|
||||
<div
|
||||
aria-label="Text C not selected"
|
||||
class=""
|
||||
id="C"
|
||||
role="button"
|
||||
tabindex="1"
|
||||
>
|
||||
<div
|
||||
class="flex"
|
||||
>
|
||||
Text C
|
||||
<span
|
||||
class="selected"
|
||||
/>
|
||||
<span
|
||||
aria-label="submenu icon"
|
||||
class="fa fa-angle-right SubMenu__icon-right-empty"
|
||||
id="channelHeaderDropdownIconRight_C"
|
||||
/>
|
||||
</div>
|
||||
<ul
|
||||
class="a11y__popup Menu dropdown-menu SubMenu"
|
||||
id="C_submenu"
|
||||
style="visibility: hidden; top: unset; right: 100%;"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</span>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
+11
-10
@@ -1,12 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {waitForElementToBeRemoved} from '@testing-library/react';
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Modal} from 'react-bootstrap';
|
||||
|
||||
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen, userEvent, waitFor, waitForElementToBeRemoved} from 'tests/react_testing_utils';
|
||||
|
||||
import SubMenuModal from './submenu_modal';
|
||||
|
||||
@@ -50,10 +47,10 @@ describe('components/submenu_modal', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const {baseElement} = renderWithContext(
|
||||
<SubMenuModal {...baseProps}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should hide on modal body click', async () => {
|
||||
@@ -91,12 +88,16 @@ describe('components/submenu_modal', () => {
|
||||
expect(action3).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should have called props.onExited when Modal.onExited is called', () => {
|
||||
const wrapper = shallow(
|
||||
test('should have called props.onExited when Modal.onExited is called', async () => {
|
||||
renderWithContext(
|
||||
<SubMenuModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.find(Modal).props().onExited!(document.createElement('div'));
|
||||
expect(baseProps.onExited).toHaveBeenCalledTimes(1);
|
||||
// Trigger modal close by clicking the body
|
||||
await userEvent.click(screen.getByTestId('SubMenuModalBody'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(baseProps.onExited).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Textbox from 'components/textbox';
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import PluginTextbox from '.';
|
||||
|
||||
const mockTextboxProps = jest.fn();
|
||||
jest.mock('components/textbox', () => {
|
||||
// Use require inside the factory: jest hoists mocks before imports run, and babel-plugin-jest-hoist
|
||||
// forbids non-mock-prefixed outer variables (e.g. React). Cast so forwardRef is typed.
|
||||
const react = require('react') as typeof import('react'); // eslint-disable-line @typescript-eslint/no-var-requires, global-require
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- forwardRef arity; ref unused in mock
|
||||
const MockTextbox = react.forwardRef((props: unknown, _ref: unknown) => {
|
||||
mockTextboxProps(props);
|
||||
return null;
|
||||
});
|
||||
MockTextbox.displayName = 'Textbox';
|
||||
return {__esModule: true, default: MockTextbox};
|
||||
});
|
||||
|
||||
describe('PluginTextbox', () => {
|
||||
const baseProps = {
|
||||
id: 'id',
|
||||
@@ -33,13 +46,18 @@ describe('PluginTextbox', () => {
|
||||
};
|
||||
|
||||
test('should rename suggestionListStyle to suggestionListPosition', () => {
|
||||
const props: React.ComponentProps<typeof PluginTextbox> = {
|
||||
const props = {
|
||||
...baseProps,
|
||||
suggestionListStyle: 'bottom',
|
||||
suggestionListStyle: 'bottom' as const,
|
||||
};
|
||||
const wrapper = shallow(<PluginTextbox {...props}/>);
|
||||
|
||||
expect(wrapper.find(Textbox).prop('suggestionListPosition')).toEqual('bottom');
|
||||
expect(wrapper.find(Textbox).prop('suggestionListStyle')).toBeUndefined();
|
||||
renderWithContext(<PluginTextbox {...props}/>);
|
||||
|
||||
expect(mockTextboxProps).toHaveBeenCalledWith(
|
||||
expect.objectContaining({suggestionListPosition: 'bottom'}),
|
||||
);
|
||||
expect(mockTextboxProps).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({suggestionListStyle: expect.anything()}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -200,6 +200,29 @@ const Providers = ({children, store, history, options}: RenderStateProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* After `render` / `renderWithContext`, runs an async `act` boundary so updates from mount effects
|
||||
* (e.g. promise chains) can commit. Does not wrap the render call itself.
|
||||
*
|
||||
* @param microtaskRounds How many times to `await Promise.resolve()` inside `act`, yielding to the
|
||||
* microtask queue between each. Increase above the default when mocked thunks resolve in sequence
|
||||
* (e.g. effect + multiple `await`s) so `setState` runs inside `act` and React does not warn.
|
||||
* @default 1
|
||||
*/
|
||||
export function runPostRenderAct(microtaskRounds: number = 1) {
|
||||
const rounds = Math.max(1, microtaskRounds);
|
||||
return act(async () => {
|
||||
const drainMicrotasks = async (remaining: number): Promise<void> => {
|
||||
if (remaining <= 0) {
|
||||
return;
|
||||
}
|
||||
await Promise.resolve();
|
||||
await drainMicrotasks(remaining - 1);
|
||||
};
|
||||
await drainMicrotasks(rounds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to use when an Enzyme test needs to wait for async code to run in a component before generating a snapshot.
|
||||
*
|
||||
|
||||
@@ -1,273 +1,413 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`messageHtmlToComponent At mention 1`] = `
|
||||
<p>
|
||||
<span
|
||||
className="mention--highlight"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
<span
|
||||
data-mention="joram"
|
||||
class="mention--highlight"
|
||||
>
|
||||
<Memo(Connect(Component))
|
||||
disableGroupHighlight={false}
|
||||
disableHighlight={false}
|
||||
mentionName="joram"
|
||||
<span
|
||||
data-mention="joram"
|
||||
>
|
||||
@joram
|
||||
</Memo(Connect(Component))>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent At mention 2`] = `
|
||||
<p>
|
||||
<span
|
||||
data-mention="joram"
|
||||
>
|
||||
<Memo(Connect(Component))
|
||||
disableGroupHighlight={false}
|
||||
disableHighlight={true}
|
||||
mentionName="joram"
|
||||
<div>
|
||||
<p>
|
||||
<span
|
||||
data-mention="joram"
|
||||
>
|
||||
@joram
|
||||
</Memo(Connect(Component))>
|
||||
</span>
|
||||
</p>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent At mention with group highlight disabled 1`] = `
|
||||
<p>
|
||||
<span
|
||||
data-mention="developers"
|
||||
>
|
||||
<Memo(Connect(Component))
|
||||
disableGroupHighlight={false}
|
||||
disableHighlight={false}
|
||||
mentionName="developers"
|
||||
<div>
|
||||
<p>
|
||||
<span
|
||||
data-mention="developers"
|
||||
>
|
||||
@developers
|
||||
</Memo(Connect(Component))>
|
||||
</span>
|
||||
</p>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent At mention with group highlight disabled 2`] = `
|
||||
<p>
|
||||
<span
|
||||
data-mention="developers"
|
||||
>
|
||||
<Memo(Connect(Component))
|
||||
disableGroupHighlight={true}
|
||||
disableHighlight={false}
|
||||
mentionName="developers"
|
||||
<div>
|
||||
<p>
|
||||
<span
|
||||
data-mention="developers"
|
||||
>
|
||||
@developers
|
||||
</Memo(Connect(Component))>
|
||||
</span>
|
||||
</p>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent Inline markdown image 1`] = `
|
||||
<div
|
||||
className="markdown-inline-img__container"
|
||||
>
|
||||
<Memo(Connect(MarkdownImage))
|
||||
alt="Mattermost"
|
||||
className="markdown-inline-img"
|
||||
imageIsLink={false}
|
||||
postId="post_id"
|
||||
postType="system_header_change"
|
||||
src="/images/icon.png"
|
||||
/>
|
||||
and a
|
||||
<a
|
||||
className="theme markdown__link"
|
||||
href="http://link"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
<div>
|
||||
<div
|
||||
class="markdown-inline-img__container"
|
||||
>
|
||||
link
|
||||
</a>
|
||||
<div
|
||||
class="file-preview__button"
|
||||
style="display: inline-block;"
|
||||
>
|
||||
<figure
|
||||
class="image-loaded-container"
|
||||
>
|
||||
<img
|
||||
alt="Mattermost"
|
||||
aria-label="file thumbnail"
|
||||
class="markdown-inline-img markdown-inline-img--scaled-down-loading"
|
||||
src="/images/icon.png"
|
||||
tabindex="0"
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
and a
|
||||
<a
|
||||
class="theme markdown__link"
|
||||
href="http://link"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
link
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent Inline markdown image where image is link 1`] = `
|
||||
<div
|
||||
className="markdown-inline-img__container"
|
||||
>
|
||||
<a
|
||||
className="theme markdown__link"
|
||||
href="http://images/icon.png"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
<div>
|
||||
<div
|
||||
class="markdown-inline-img__container"
|
||||
>
|
||||
<Memo(Connect(MarkdownImage))
|
||||
alt="Mattermost"
|
||||
className="markdown-inline-img"
|
||||
imageIsLink={true}
|
||||
postId="post_id"
|
||||
postType="system_header_change"
|
||||
src="images/icon.png"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
class="theme markdown__link"
|
||||
href="http://images/icon.png"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
class="file-preview__button"
|
||||
style="display: inline-block;"
|
||||
>
|
||||
<figure
|
||||
class="image-loaded-container"
|
||||
>
|
||||
<img
|
||||
alt="Mattermost"
|
||||
aria-label="file thumbnail"
|
||||
class="markdown-inline-img markdown-inline-img--scaled-down-loading"
|
||||
src="images/icon.png"
|
||||
tabindex="0"
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent Remote at mention 1`] = `
|
||||
<p>
|
||||
<span
|
||||
className="mention--highlight"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
<span
|
||||
data-mention="joram"
|
||||
class="mention--highlight"
|
||||
>
|
||||
<Memo(Connect(Component))
|
||||
disableGroupHighlight={false}
|
||||
disableHighlight={false}
|
||||
mentionName="joram"
|
||||
<span
|
||||
data-mention="joram"
|
||||
>
|
||||
@joram
|
||||
</Memo(Connect(Component))>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent Remote at mention 2`] = `
|
||||
<p>
|
||||
<span
|
||||
data-mention="joram"
|
||||
>
|
||||
<Memo(Connect(Component))
|
||||
disableGroupHighlight={false}
|
||||
disableHighlight={true}
|
||||
mentionName="joram"
|
||||
<div>
|
||||
<p>
|
||||
<span
|
||||
data-mention="joram"
|
||||
>
|
||||
@joram
|
||||
</Memo(Connect(Component))>
|
||||
</span>
|
||||
</p>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent citation links should not render normal links as InlineEntityLink 1`] = `
|
||||
<p>
|
||||
<a
|
||||
className="theme markdown__link"
|
||||
href="http://localhost:8065/team/pl/postid"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
post
|
||||
</a>
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
<a
|
||||
class="theme markdown__link"
|
||||
href="http://localhost:8065/team/pl/postid"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
post
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent citation links should render citation link using InlineEntityLink 1`] = `
|
||||
<p>
|
||||
<InlineEntityLink
|
||||
text={
|
||||
Array [
|
||||
"post",
|
||||
]
|
||||
}
|
||||
url="http://localhost:8065/team/pl/postid?view=citation"
|
||||
/>
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
<a
|
||||
aria-label="Go to post"
|
||||
class="inline-entity-link"
|
||||
href="http://localhost:8065/team/pl/postid?view=citation"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="14"
|
||||
version="1.1"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.59,13.41C11,13.8 11,14.44 10.59,14.83C10.2,15.22 9.56,15.22 9.17,14.83C7.22,12.88 7.22,9.71 9.17,7.76V7.76L12.71,4.22C14.66,2.27 17.83,2.27 19.78,4.22C21.73,6.17 21.73,9.34 19.78,11.29L18.29,12.78C18.3,11.96 18.17,11.14 17.89,10.36L18.36,9.88C19.54,8.71 19.54,6.81 18.36,5.64C17.19,4.46 15.29,4.46 14.12,5.64L10.59,9.17C9.41,10.34 9.41,12.24 10.59,13.41M13.41,9.17C13.8,8.78 14.44,8.78 14.83,9.17C16.78,11.12 16.78,14.29 14.83,16.24V16.24L11.29,19.78C9.34,21.73 6.17,21.73 4.22,19.78C2.27,17.83 2.27,14.66 4.22,12.71L5.71,11.22C5.7,12.04 5.83,12.86 6.11,13.65L5.64,14.12C4.46,15.29 4.46,17.19 5.64,18.36C6.81,19.54 8.71,19.54 9.88,18.36L13.41,14.83C14.59,13.66 14.59,11.76 13.41,10.59C13,10.2 13,9.56 13.41,9.17Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent html 1`] = `
|
||||
<CodeBlock
|
||||
code="<div>This is a html div</div>"
|
||||
language="html"
|
||||
searchedContent=""
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
class="post-code"
|
||||
>
|
||||
<div
|
||||
class="post-code__overlay"
|
||||
>
|
||||
<span
|
||||
aria-label="Copy code"
|
||||
class="post-code__clipboard"
|
||||
role="button"
|
||||
>
|
||||
<i
|
||||
class="icon icon-content-copy"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="post-code__language"
|
||||
>
|
||||
HTML, XML
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="hljs"
|
||||
>
|
||||
<div
|
||||
class="post-code__line-numbers"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
<code>
|
||||
<div>This is a html div</div>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent latex 1`] = `
|
||||
Array [
|
||||
<div>
|
||||
<p>
|
||||
This is some latex!
|
||||
</p>,
|
||||
"
|
||||
",
|
||||
<Memo(Connect(Component))
|
||||
content="x^2 + y^2 = z^2"
|
||||
/>,
|
||||
<Memo(Connect(Component))
|
||||
content="F_m - 2 = F_0 F_1 \\\\dots F_{m-1}"
|
||||
/>,
|
||||
</p>
|
||||
|
||||
|
||||
<div
|
||||
class="post-code"
|
||||
>
|
||||
<div
|
||||
class="post-code__overlay"
|
||||
>
|
||||
<span
|
||||
aria-label="Copy code"
|
||||
class="post-code__clipboard"
|
||||
role="button"
|
||||
>
|
||||
<i
|
||||
class="icon icon-content-copy"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="post-code__language"
|
||||
>
|
||||
LaTeX
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="hljs"
|
||||
>
|
||||
<div
|
||||
class="post-code__line-numbers"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
<code>
|
||||
x^2 + y^2 = z^2
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="post-code"
|
||||
>
|
||||
<div
|
||||
class="post-code__overlay"
|
||||
>
|
||||
<span
|
||||
aria-label="Copy code"
|
||||
class="post-code__clipboard"
|
||||
role="button"
|
||||
>
|
||||
<i
|
||||
class="icon icon-content-copy"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="post-code__language"
|
||||
>
|
||||
LaTeX
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="hljs"
|
||||
>
|
||||
<div
|
||||
class="post-code__line-numbers"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
<code>
|
||||
F_m - 2 = F_0 F_1 \\dots F_{m-1}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
That was some latex!
|
||||
</p>,
|
||||
]
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent link with enabled a tooltip plugin 1`] = `
|
||||
<p>
|
||||
lorem ipsum
|
||||
<PluginLinkTooltip
|
||||
nodeAttributes={
|
||||
Object {
|
||||
"className": "theme markdown__link",
|
||||
"href": "http://www.dolor.com",
|
||||
"rel": "noreferrer",
|
||||
"target": "_blank",
|
||||
}
|
||||
}
|
||||
>
|
||||
www.dolor.com
|
||||
</PluginLinkTooltip>
|
||||
sit amet
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
lorem ipsum
|
||||
<a
|
||||
class="theme markdown__link"
|
||||
href="http://www.dolor.com"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
www.dolor.com
|
||||
</a>
|
||||
sit amet
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent link without enabled tooltip plugins 1`] = `
|
||||
<p>
|
||||
lorem ipsum
|
||||
<a
|
||||
className="theme markdown__link"
|
||||
href="http://www.dolor.com"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
www.dolor.com
|
||||
</a>
|
||||
sit amet
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
lorem ipsum
|
||||
<a
|
||||
class="theme markdown__link"
|
||||
href="http://www.dolor.com"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
www.dolor.com
|
||||
</a>
|
||||
sit amet
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent plain text 1`] = `
|
||||
<p>
|
||||
Hello, world!
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
Hello, world!
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent typescript 1`] = `
|
||||
<CodeBlock
|
||||
code="const myFunction = () => {
|
||||
<div>
|
||||
<div
|
||||
class="post-code"
|
||||
>
|
||||
<div
|
||||
class="post-code__overlay"
|
||||
>
|
||||
<span
|
||||
aria-label="Copy code"
|
||||
class="post-code__clipboard"
|
||||
role="button"
|
||||
>
|
||||
<i
|
||||
class="icon icon-content-copy"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="post-code__language"
|
||||
>
|
||||
TypeScript
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="hljs"
|
||||
>
|
||||
<div
|
||||
class="post-code__line-numbers"
|
||||
>
|
||||
1
|
||||
2
|
||||
3
|
||||
</div>
|
||||
<code>
|
||||
const myFunction = () => {
|
||||
console.log('This is a meaningful function');
|
||||
};"
|
||||
language="typescript"
|
||||
searchedContent=""
|
||||
/>
|
||||
};
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`messageHtmlToComponent typescript 2`] = `
|
||||
<p>
|
||||
Text before typescript codeblock
|
||||
<div>
|
||||
<p>
|
||||
Text before typescript codeblock
|
||||
|
||||
<span
|
||||
className="codespan__pre-wrap"
|
||||
>
|
||||
<code>
|
||||
typescript
|
||||
<span
|
||||
class="codespan__pre-wrap"
|
||||
>
|
||||
<code>
|
||||
typescript
|
||||
const myFunction = () => {
|
||||
console.log('This is a test function');
|
||||
};
|
||||
</code>
|
||||
</span>
|
||||
|
||||
</code>
|
||||
</span>
|
||||
|
||||
text after typescript block
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {AnchorHTMLAttributes} from 'react';
|
||||
|
||||
import AtMention from 'components/at_mention';
|
||||
import InlineEntityLink from 'components/inline_entity_link';
|
||||
import MarkdownImage from 'components/markdown_image';
|
||||
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import Constants from 'utils/constants';
|
||||
import EmojiMap from 'utils/emoji_map';
|
||||
@@ -21,7 +17,8 @@ describe('messageHtmlToComponent', () => {
|
||||
const input = 'Hello, world!';
|
||||
const html = TextFormatting.formatText(input, {}, emptyEmojiMap);
|
||||
|
||||
expect(messageHtmlToComponent(html)).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<>{messageHtmlToComponent(html)}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('latex', () => {
|
||||
@@ -37,7 +34,8 @@ F_m - 2 = F_0 F_1 \\dots F_{m-1}
|
||||
That was some latex!`;
|
||||
const html = TextFormatting.formatText(input, {}, emptyEmojiMap);
|
||||
|
||||
expect(messageHtmlToComponent(html)).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<>{messageHtmlToComponent(html)}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('typescript', () => {
|
||||
@@ -49,7 +47,8 @@ const myFunction = () => {
|
||||
`;
|
||||
const html = TextFormatting.formatText(input, {}, emptyEmojiMap);
|
||||
|
||||
expect(messageHtmlToComponent(html, {postId: 'randompostid'})).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<>{messageHtmlToComponent(html, {postId: 'randompostid'})}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('html', () => {
|
||||
@@ -59,21 +58,24 @@ const myFunction = () => {
|
||||
`;
|
||||
const html = TextFormatting.formatText(input, {}, emptyEmojiMap);
|
||||
|
||||
expect(messageHtmlToComponent(html, {postId: 'randompostid'})).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<>{messageHtmlToComponent(html, {postId: 'randompostid'})}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('link without enabled tooltip plugins', () => {
|
||||
const input = 'lorem ipsum www.dolor.com sit amet';
|
||||
const html = TextFormatting.formatText(input, {}, emptyEmojiMap);
|
||||
|
||||
expect(messageHtmlToComponent(html)).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<>{messageHtmlToComponent(html)}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('link with enabled a tooltip plugin', () => {
|
||||
const input = 'lorem ipsum www.dolor.com sit amet';
|
||||
const html = TextFormatting.formatText(input, {}, emptyEmojiMap);
|
||||
|
||||
expect(messageHtmlToComponent(html, {hasPluginTooltips: true})).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<>{messageHtmlToComponent(html, {hasPluginTooltips: true})}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Inline markdown image', () => {
|
||||
@@ -85,8 +87,12 @@ const myFunction = () => {
|
||||
postId: 'post_id',
|
||||
postType: Constants.PostTypes.HEADER_CHANGE,
|
||||
});
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(shallow(component).find(MarkdownImage).prop('imageIsLink')).toBe(false);
|
||||
const {container} = renderWithContext(<>{component}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// imageIsLink=false means the img is NOT wrapped in an <a> tag
|
||||
const img = container.querySelector('img');
|
||||
expect(img?.closest('a')).toBeNull();
|
||||
});
|
||||
|
||||
test('Inline markdown image where image is link', () => {
|
||||
@@ -98,8 +104,12 @@ const myFunction = () => {
|
||||
postId: 'post_id',
|
||||
postType: Constants.PostTypes.HEADER_CHANGE,
|
||||
});
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(shallow(component).find(MarkdownImage).prop('imageIsLink')).toBe(true);
|
||||
const {container} = renderWithContext(<>{component}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// imageIsLink=true means the img IS wrapped in an <a> tag
|
||||
const img = container.querySelector('img');
|
||||
expect(img?.closest('a')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('At mention', () => {
|
||||
@@ -107,16 +117,24 @@ const myFunction = () => {
|
||||
let html = TextFormatting.formatText('@joram', options, emptyEmojiMap);
|
||||
|
||||
let component = messageHtmlToComponent(html, {mentionHighlight: true});
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(shallow(component).find(AtMention).prop('disableHighlight')).toBe(false);
|
||||
const {container, unmount} = renderWithContext(<>{component}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// When mentionHighlight is true, disableHighlight is false - the mention--highlight class wraps the mention
|
||||
expect(container.querySelector('.mention--highlight')).toBeInTheDocument();
|
||||
|
||||
unmount();
|
||||
|
||||
options.mentionHighlight = false;
|
||||
|
||||
html = TextFormatting.formatText('@joram', options, emptyEmojiMap);
|
||||
|
||||
component = messageHtmlToComponent(html, {mentionHighlight: false});
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(shallow(component).find(AtMention).prop('disableHighlight')).toBe(true);
|
||||
const {container: container2} = renderWithContext(<>{component}</>);
|
||||
expect(container2).toMatchSnapshot();
|
||||
|
||||
// When mentionHighlight is false, disableHighlight is true - no mention--highlight class
|
||||
expect(container2.querySelector('.mention--highlight')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('At mention with group highlight disabled', () => {
|
||||
@@ -124,16 +142,18 @@ const myFunction = () => {
|
||||
let html = TextFormatting.formatText('@developers', options, emptyEmojiMap);
|
||||
|
||||
let component = messageHtmlToComponent(html, {disableGroupHighlight: false});
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(shallow(component).find(AtMention).prop('disableGroupHighlight')).toBe(false);
|
||||
const {container, unmount} = renderWithContext(<>{component}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
unmount();
|
||||
|
||||
options.disableGroupHighlight = true;
|
||||
|
||||
html = TextFormatting.formatText('@developers', options, emptyEmojiMap);
|
||||
|
||||
component = messageHtmlToComponent(html, {disableGroupHighlight: true});
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(shallow(component).find(AtMention).prop('disableGroupHighlight')).toBe(true);
|
||||
const {container: container2} = renderWithContext(<>{component}</>);
|
||||
expect(container2).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Remote at mention', () => {
|
||||
@@ -141,16 +161,24 @@ const myFunction = () => {
|
||||
let html = TextFormatting.formatText('@joram', options, emptyEmojiMap);
|
||||
|
||||
let component = messageHtmlToComponent(html, {mentionHighlight: true});
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(shallow(component).find(AtMention).prop('disableHighlight')).toBe(false);
|
||||
const {container, unmount} = renderWithContext(<>{component}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// When mentionHighlight is true, disableHighlight is false - the mention--highlight class wraps the mention
|
||||
expect(container.querySelector('.mention--highlight')).toBeInTheDocument();
|
||||
|
||||
unmount();
|
||||
|
||||
options.mentionHighlight = false;
|
||||
|
||||
html = TextFormatting.formatText('@joram', options, emptyEmojiMap);
|
||||
|
||||
component = messageHtmlToComponent(html, {mentionHighlight: false});
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(shallow(component).find(AtMention).prop('disableHighlight')).toBe(true);
|
||||
const {container: container2} = renderWithContext(<>{component}</>);
|
||||
expect(container2).toMatchSnapshot();
|
||||
|
||||
// When mentionHighlight is false, disableHighlight is true - no mention--highlight class
|
||||
expect(container2.querySelector('.mention--highlight')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('typescript', () => {
|
||||
@@ -164,7 +192,8 @@ const myFunction = () => {
|
||||
|
||||
const html = TextFormatting.formatText(input, {}, emptyEmojiMap);
|
||||
|
||||
expect(messageHtmlToComponent(html)).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<>{messageHtmlToComponent(html)}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('citation links', () => {
|
||||
@@ -173,11 +202,13 @@ const myFunction = () => {
|
||||
const html = TextFormatting.formatText(input, {}, emptyEmojiMap);
|
||||
|
||||
const component = messageHtmlToComponent(html);
|
||||
expect(component).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<>{component}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
const wrapper = shallow(component);
|
||||
expect(wrapper.find(InlineEntityLink).exists()).toBe(true);
|
||||
expect(wrapper.find(InlineEntityLink).prop('url')).toBe('http://localhost:8065/team/pl/postid?view=citation');
|
||||
// InlineEntityLink renders an anchor with the citation URL and class 'inline-entity-link'
|
||||
const link = container.querySelector('a.inline-entity-link');
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link?.getAttribute('href')).toBe('http://localhost:8065/team/pl/postid?view=citation');
|
||||
});
|
||||
|
||||
test('should not render normal links as InlineEntityLink', () => {
|
||||
@@ -185,11 +216,12 @@ const myFunction = () => {
|
||||
const html = TextFormatting.formatText(input, {}, emptyEmojiMap);
|
||||
|
||||
const component = messageHtmlToComponent(html);
|
||||
expect(component).toMatchSnapshot();
|
||||
const {container} = renderWithContext(<>{component}</>);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
const wrapper = shallow(component);
|
||||
expect(wrapper.find(InlineEntityLink).exists()).toBe(false);
|
||||
expect(wrapper.find('a').exists()).toBe(true);
|
||||
// Normal links should NOT have the inline-entity-link class
|
||||
expect(container.querySelector('a.inline-entity-link')).not.toBeInTheDocument();
|
||||
expect(container.querySelector('a')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user