mirror of
https://github.com/wassname/talk.git
synced 2026-08-01 13:00:55 +08:00
[CORL-166] Live Updates on Mod Queues (#2368)
* feat: client implementation of subscriptions and modqueue live counts * fix: unit tests * feat: live status update in moderation * feat: live update of new comments in moderation * chore: View New instead of View More * feat: fade in transition for new comments * chore: turn websocket proxy back on * feat: initial server impl * fix: make it work :-) * fix: add box shadow * chore: make test subscriptions only support 1 top level field following the spec * fix: linting * feat: support clientID * fix: linting * feat: support commentStatusUpdated subscription * fix: disabled styles for approve and reject button * feat: show moderated by system and update flags * feat: support metrics recording on websocket connections * fix: handle when same comment enters but leaves again
This commit is contained in:
@@ -418,8 +418,12 @@ export const baseComment = createFixture<GQLComment>({
|
||||
author: users.commenters[0],
|
||||
body: "Comment Body",
|
||||
createdAt: "2018-07-06T18:24:00.000Z",
|
||||
status: GQLCOMMENT_STATUS.NONE,
|
||||
tags: [],
|
||||
status: GQLCOMMENT_STATUS.NONE,
|
||||
statusHistory: {
|
||||
edges: [],
|
||||
pageInfo: { endCursor: null, hasNextPage: false },
|
||||
},
|
||||
actionCounts: {
|
||||
flag: {
|
||||
reasons: {
|
||||
@@ -438,6 +442,8 @@ export const baseComment = createFixture<GQLComment>({
|
||||
nodes: [],
|
||||
},
|
||||
story: stories[0],
|
||||
// TODO: Should be allowed to pass null here..
|
||||
parent: undefined,
|
||||
});
|
||||
|
||||
export const unmoderatedComments = createFixtures<GQLComment>(
|
||||
@@ -577,6 +583,32 @@ export const reportedComments = createFixtures<GQLComment>(
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "comment-3",
|
||||
revision: {
|
||||
id: "comment-3-revision-3",
|
||||
},
|
||||
permalink: "http://localhost/comment/3",
|
||||
status: GQLCOMMENT_STATUS.PREMOD,
|
||||
author: users.commenters[3],
|
||||
body: "World peace at last",
|
||||
actionCounts: {
|
||||
flag: {
|
||||
reasons: {
|
||||
COMMENT_REPORTED_SPAM: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
nodes: [
|
||||
{
|
||||
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_SPAM,
|
||||
flagger: users.commenters[2],
|
||||
additionalDetails: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
baseComment
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,849 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`approves comment in rejected queue: count should be 1 1`] = `
|
||||
<span
|
||||
className="Counter-root Counter-colorInherit"
|
||||
data-testid="moderate-navigation-reported-count"
|
||||
>
|
||||
<span
|
||||
className="Counter-text"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`approves comment in rejected queue: dangling 1`] = `
|
||||
<div
|
||||
className="Card-root ModerateCard-root ModerateCard-dangling"
|
||||
data-testid="moderate-comment-comment-0"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-topBar"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
className="Box-root Typography-root Typography-heading4 Typography-colorTextPrimary ModerateCard-username Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
<button
|
||||
className="BaseButton-root FeatureButton-root"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Feature
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary ModerateCard-content CommentContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "This is the last random sentence I will be writing and I am going to stop mid-sent",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="http://localhost/comment/0"
|
||||
target="_blank"
|
||||
>
|
||||
View Context
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-xs TextLink-icon"
|
||||
>
|
||||
open_in_new
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
className="ModerateCard-story"
|
||||
>
|
||||
Story
|
||||
</span>
|
||||
:
|
||||
<span
|
||||
className="ModerateCard-storyTitle"
|
||||
>
|
||||
Finally a Cure for Cancer
|
||||
</span>
|
||||
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="/admin/moderate/story-1"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Moderate Story
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<div
|
||||
className="Box-root Flex-root ModerateCard-aside ModerateCard-asideWithoutReplyTo Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionColumn gutter"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-decision"
|
||||
>
|
||||
Decision
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<button
|
||||
aria-label="Reject"
|
||||
className="BaseButton-root RejectButton-root"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg RejectButton-icon"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Approve"
|
||||
className="BaseButton-root ApproveButton-root ApproveButton-invert"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg ApproveButton-icon"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders rejected queue with comments 1`] = `
|
||||
<div
|
||||
className="MainLayout-root"
|
||||
data-testid="moderate-main-container"
|
||||
>
|
||||
<main
|
||||
className="Moderate-main"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root Queue-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Card-root ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-0"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-topBar"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
className="Box-root Typography-root Typography-heading4 Typography-colorTextPrimary ModerateCard-username Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
<button
|
||||
className="BaseButton-root FeatureButton-root"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Feature
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary ModerateCard-content CommentContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "This is the last random sentence I will be writing and I am going to stop mid-sent",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="http://localhost/comment/0"
|
||||
target="_blank"
|
||||
>
|
||||
View Context
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-xs TextLink-icon"
|
||||
>
|
||||
open_in_new
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
className="ModerateCard-story"
|
||||
>
|
||||
Story
|
||||
</span>
|
||||
:
|
||||
<span
|
||||
className="ModerateCard-storyTitle"
|
||||
>
|
||||
Finally a Cure for Cancer
|
||||
</span>
|
||||
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="/admin/moderate/story-1"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Moderate Story
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<div
|
||||
className="Box-root Flex-root ModerateCard-aside ModerateCard-asideWithoutReplyTo Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionColumn gutter"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-decision"
|
||||
>
|
||||
Decision
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<button
|
||||
aria-label="Reject"
|
||||
className="BaseButton-root RejectButton-root RejectButton-invert"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg RejectButton-icon"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Approve"
|
||||
className="BaseButton-root ApproveButton-root"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg ApproveButton-icon"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Card-root ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-1"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-topBar"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
className="Box-root Typography-root Typography-heading4 Typography-colorTextPrimary ModerateCard-username Username-root"
|
||||
>
|
||||
Ngoc
|
||||
</span>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
<button
|
||||
className="BaseButton-root FeatureButton-root"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Feature
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary ModerateCard-content CommentContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Don't fool with me",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="http://localhost/comment/1"
|
||||
target="_blank"
|
||||
>
|
||||
View Context
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-xs TextLink-icon"
|
||||
>
|
||||
open_in_new
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
className="ModerateCard-story"
|
||||
>
|
||||
Story
|
||||
</span>
|
||||
:
|
||||
<span
|
||||
className="ModerateCard-storyTitle"
|
||||
>
|
||||
Finally a Cure for Cancer
|
||||
</span>
|
||||
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="/admin/moderate/story-1"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Moderate Story
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-1"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<div
|
||||
className="Box-root Flex-root ModerateCard-aside ModerateCard-asideWithoutReplyTo Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionColumn gutter"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-decision"
|
||||
>
|
||||
Decision
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<button
|
||||
aria-label="Reject"
|
||||
className="BaseButton-root RejectButton-root RejectButton-invert"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg RejectButton-icon"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Approve"
|
||||
className="BaseButton-root ApproveButton-root"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg ApproveButton-icon"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders rejected queue with comments and load more 1`] = `
|
||||
<div
|
||||
className="Card-root ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-2"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-topBar"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
className="Box-root Typography-root Typography-heading4 Typography-colorTextPrimary ModerateCard-username Username-root"
|
||||
>
|
||||
Max
|
||||
</span>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
<button
|
||||
className="BaseButton-root FeatureButton-root"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Feature
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary ModerateCard-content CommentContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "I think I deserve better",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="http://localhost/comment/2"
|
||||
target="_blank"
|
||||
>
|
||||
View Context
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-xs TextLink-icon"
|
||||
>
|
||||
open_in_new
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
className="ModerateCard-story"
|
||||
>
|
||||
Story
|
||||
</span>
|
||||
:
|
||||
<span
|
||||
className="ModerateCard-storyTitle"
|
||||
>
|
||||
Finally a Cure for Cancer
|
||||
</span>
|
||||
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="/admin/moderate/story-1"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Moderate Story
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-2"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<div
|
||||
className="Box-root Flex-root ModerateCard-aside ModerateCard-asideWithoutReplyTo Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionColumn gutter"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-decision"
|
||||
>
|
||||
Decision
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<button
|
||||
aria-label="Reject"
|
||||
className="BaseButton-root RejectButton-root RejectButton-invert"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg RejectButton-icon"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Approve"
|
||||
className="BaseButton-root ApproveButton-root"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg ApproveButton-icon"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,255 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`all stories active search with no results 1`] = `
|
||||
<div
|
||||
aria-expanded={true}
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Search or jump to story"
|
||||
aria-owns="moderate-searchBar-listBox"
|
||||
className="SubBar-root Bar-root"
|
||||
data-testid="moderate-searchBar-container"
|
||||
role="combobox"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root SubBar-container Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
>
|
||||
<div
|
||||
className="Backdrop-root Backdrop-active Bar-bumpZIndex"
|
||||
/>
|
||||
<form
|
||||
aria-label="Stories"
|
||||
className="Bar-bumpZIndex"
|
||||
onMouseDown={[Function]}
|
||||
onSubmit={[Function]}
|
||||
role="search"
|
||||
>
|
||||
<div
|
||||
className="Popover-root"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Box-root Flex-root Field-root Flex-flex Flex-alignStretch"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Field-begin Flex-flex Flex-alignCenter"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md Field-searchIcon"
|
||||
>
|
||||
search
|
||||
</span>
|
||||
<div
|
||||
className="Field-beginStories"
|
||||
>
|
||||
Stories:
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="moderate-searchBar-listBox"
|
||||
aria-label="Search or jump to story..."
|
||||
autoComplete="off"
|
||||
className="Field-input"
|
||||
name="search"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Use quotation marks around each search term (e.g. “team”, “St. Louis”)"
|
||||
spellCheck={false}
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="Box-root Flex-root Field-end Flex-flex Flex-alignCenter"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Field-searchButton"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden={false}
|
||||
aria-labelledby="moderate-searchBar-popover-ariainfo"
|
||||
id="moderate-searchBar-popover"
|
||||
role="popup"
|
||||
>
|
||||
<div
|
||||
className="Popover-popover Bar-popover Popover-bottom"
|
||||
style={
|
||||
Object {
|
||||
"left": 0,
|
||||
"opacity": 0,
|
||||
"pointerEvents": "none",
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ul
|
||||
className="Bar-listBox"
|
||||
id="moderate-searchBar-listBox"
|
||||
role="listbox"
|
||||
>
|
||||
<ul
|
||||
aria-labelledby="moderate-searchBar-context-title"
|
||||
className="Group-root"
|
||||
id="moderate-searchBar-context"
|
||||
role="group"
|
||||
>
|
||||
<li
|
||||
className="Group-title"
|
||||
id="moderate-searchBar-context-title"
|
||||
>
|
||||
Currently moderating
|
||||
</li>
|
||||
<li
|
||||
aria-selected={false}
|
||||
className="Option-root"
|
||||
id="moderate-searchBar-listBoxOption-0"
|
||||
onClick={[Function]}
|
||||
role="option"
|
||||
>
|
||||
<a
|
||||
className="Option-link"
|
||||
href="/admin/moderate"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div
|
||||
className="Option-container"
|
||||
>
|
||||
<div
|
||||
className="Option-title"
|
||||
>
|
||||
<div
|
||||
className="AriaInfo-root"
|
||||
>
|
||||
Go to
|
||||
</div>
|
||||
<span>
|
||||
All stories
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="Option-details"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`all stories active search with too many results 1`] = `
|
||||
<li
|
||||
aria-selected={false}
|
||||
className="SeeAllOption-root"
|
||||
id="moderate-searchBar-listBoxOption-3"
|
||||
onClick={[Function]}
|
||||
role="option"
|
||||
>
|
||||
<a
|
||||
className="SeeAllOption-link"
|
||||
href="/admin/stories?q=InterestingStory"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<span>
|
||||
See all results
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm SeeAllOption-icon"
|
||||
>
|
||||
arrow_forward
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
|
||||
exports[`all stories renders search bar 1`] = `
|
||||
<div
|
||||
aria-expanded={false}
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Search or jump to story"
|
||||
aria-owns="moderate-searchBar-listBox"
|
||||
className="SubBar-root Bar-root"
|
||||
data-testid="moderate-searchBar-container"
|
||||
role="combobox"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root SubBar-container Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
>
|
||||
<div
|
||||
className="Backdrop-root Bar-bumpZIndex"
|
||||
/>
|
||||
<form
|
||||
aria-label="Stories"
|
||||
className="Bar-bumpZIndex"
|
||||
onMouseDown={[Function]}
|
||||
onSubmit={[Function]}
|
||||
role="search"
|
||||
>
|
||||
<div
|
||||
className="Popover-root"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Box-root Flex-root Field-root Flex-flex Flex-alignStretch"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Field-begin Flex-flex Flex-alignCenter"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md Field-searchIcon"
|
||||
>
|
||||
search
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="moderate-searchBar-listBox"
|
||||
aria-label="Search or jump to story..."
|
||||
autoComplete="off"
|
||||
className="Field-input Field-inputWithTitle"
|
||||
name="search"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="All stories"
|
||||
spellCheck={false}
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="Box-root Flex-root Field-end Flex-flex Flex-alignCenter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
aria-labelledby="moderate-searchBar-popover-ariainfo"
|
||||
id="moderate-searchBar-popover"
|
||||
role="popup"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,586 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`approves single comment 1`] = `
|
||||
<div
|
||||
className="Card-root ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-0"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-topBar"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
className="Box-root Typography-root Typography-heading4 Typography-colorTextPrimary ModerateCard-username Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
<button
|
||||
className="BaseButton-root FeatureButton-root"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Feature
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary ModerateCard-content CommentContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "This is the last random sentence I will be writing and I am going to stop mid-sent",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="http://localhost/comment/0"
|
||||
target="_blank"
|
||||
>
|
||||
View Context
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-xs TextLink-icon"
|
||||
>
|
||||
open_in_new
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<div
|
||||
className="Box-root Flex-root ModerateCard-aside ModerateCard-asideWithoutReplyTo Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionColumn gutter"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-decision"
|
||||
>
|
||||
Decision
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<button
|
||||
aria-label="Reject"
|
||||
className="BaseButton-root RejectButton-root"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg RejectButton-icon"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Approve"
|
||||
className="BaseButton-root ApproveButton-root ApproveButton-invert"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg ApproveButton-icon"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`rejects single comment 1`] = `
|
||||
<div
|
||||
className="Card-root ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-0"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-topBar"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
className="Box-root Typography-root Typography-heading4 Typography-colorTextPrimary ModerateCard-username Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
<button
|
||||
className="BaseButton-root FeatureButton-root"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Feature
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary ModerateCard-content CommentContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "This is the last random sentence I will be writing and I am going to stop mid-sent",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="http://localhost/comment/0"
|
||||
target="_blank"
|
||||
>
|
||||
View Context
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-xs TextLink-icon"
|
||||
>
|
||||
open_in_new
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<div
|
||||
className="Box-root Flex-root ModerateCard-aside ModerateCard-asideWithoutReplyTo Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionColumn gutter"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-decision"
|
||||
>
|
||||
Decision
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<button
|
||||
aria-label="Reject"
|
||||
className="BaseButton-root RejectButton-root RejectButton-invert"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg RejectButton-icon"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Approve"
|
||||
className="BaseButton-root ApproveButton-root"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg ApproveButton-icon"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders single comment view 1`] = `
|
||||
<div
|
||||
data-testid="single-moderate-container"
|
||||
>
|
||||
<div
|
||||
className="SubBar-root SingleModerate-subBar SubBar-gutterBegin SubBar-gutterEnd"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root SubBar-container Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
>
|
||||
<a
|
||||
className="SingleModerate-subBarBegin"
|
||||
href="/admin/moderate/"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Go to moderation queues
|
||||
</a>
|
||||
<div
|
||||
className="SingleModerate-subBarTitle"
|
||||
>
|
||||
Single Comment View
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="SingleModerate-background"
|
||||
/>
|
||||
<div
|
||||
className="MainLayout-root"
|
||||
>
|
||||
<main
|
||||
className="SingleModerate-main"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root Queue-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Card-root ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-0"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-topBar"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
className="Box-root Typography-root Typography-heading4 Typography-colorTextPrimary ModerateCard-username Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
<button
|
||||
className="BaseButton-root FeatureButton-root"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Feature
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary ModerateCard-content CommentContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "This is the last random sentence I will be writing and I am going to stop mid-sent",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
className="TextLink-root ModerateCard-link"
|
||||
href="http://localhost/comment/0"
|
||||
target="_blank"
|
||||
>
|
||||
View Context
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-xs TextLink-icon"
|
||||
>
|
||||
open_in_new
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<div
|
||||
className="Box-root Flex-root ModerateCard-aside ModerateCard-asideWithoutReplyTo Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionColumn gutter"
|
||||
>
|
||||
<div
|
||||
className="ModerateCard-decision"
|
||||
>
|
||||
Decision
|
||||
</div>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
|
||||
>
|
||||
<button
|
||||
aria-label="Reject"
|
||||
className="BaseButton-root RejectButton-root"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg RejectButton-icon"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Approve"
|
||||
className="BaseButton-root ApproveButton-root"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg ApproveButton-icon"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,127 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`tab bar renders tab bar (empty queues) 1`] = `
|
||||
<div
|
||||
className="SubBar-root"
|
||||
data-testid="moderate-tabBar-container"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root SubBar-container Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
>
|
||||
<nav
|
||||
className="Navigation-root"
|
||||
>
|
||||
<ul
|
||||
className="Navigation-ul"
|
||||
>
|
||||
<li
|
||||
className="NavigationItem-root"
|
||||
>
|
||||
<a
|
||||
className="NavigationItem-anchor NavigationItem-active"
|
||||
href="/admin/moderate/reported"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
flag
|
||||
</span>
|
||||
<span>
|
||||
reported
|
||||
</span>
|
||||
<span
|
||||
className="Counter-root Counter-colorInherit"
|
||||
data-testid="moderate-navigation-reported-count"
|
||||
>
|
||||
<span
|
||||
className="Counter-text"
|
||||
>
|
||||
0
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className="NavigationItem-root"
|
||||
>
|
||||
<a
|
||||
className="NavigationItem-anchor"
|
||||
href="/admin/moderate/pending"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
access_time
|
||||
</span>
|
||||
<span>
|
||||
Pending
|
||||
</span>
|
||||
<span
|
||||
className="Counter-root Counter-colorInherit"
|
||||
data-testid="moderate-navigation-pending-count"
|
||||
>
|
||||
<span
|
||||
className="Counter-text"
|
||||
>
|
||||
0
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className="NavigationItem-root"
|
||||
>
|
||||
<a
|
||||
className="NavigationItem-anchor"
|
||||
href="/admin/moderate/unmoderated"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
forum
|
||||
</span>
|
||||
<span>
|
||||
unmoderated
|
||||
</span>
|
||||
<span
|
||||
className="Counter-root Counter-colorInherit"
|
||||
data-testid="moderate-navigation-unmoderated-count"
|
||||
>
|
||||
<span
|
||||
className="Counter-text"
|
||||
>
|
||||
0
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className="NavigationItem-root"
|
||||
>
|
||||
<a
|
||||
className="NavigationItem-anchor"
|
||||
href="/admin/moderate/rejected"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
cancel
|
||||
</span>
|
||||
<span>
|
||||
rejected
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,123 @@
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import {
|
||||
GQLComment,
|
||||
GQLMODERATION_QUEUE,
|
||||
GQLResolver,
|
||||
SubscriptionToCommentEnteredModerationQueueResolver,
|
||||
} from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
emptyRejectedComments,
|
||||
reportedComments,
|
||||
settings,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
});
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context, subscriptionHandler };
|
||||
}
|
||||
|
||||
it("live update count", async () => {
|
||||
const { testRenderer, subscriptionHandler } = await createTestRenderer();
|
||||
const verifyCount = (queue: GQLMODERATION_QUEUE, no: number) => {
|
||||
within(
|
||||
within(testRenderer.root).getByTestID(
|
||||
`moderate-navigation-${queue.toLocaleLowerCase()}-count`
|
||||
)
|
||||
).getByText(no.toString());
|
||||
};
|
||||
const commentEntered = (queue: GQLMODERATION_QUEUE, comment: GQLComment) => {
|
||||
subscriptionHandler.dispatch<
|
||||
SubscriptionToCommentEnteredModerationQueueResolver
|
||||
>("commentEnteredModerationQueue", variables => {
|
||||
if (variables.queue && variables.queue !== queue) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
queue,
|
||||
comment,
|
||||
};
|
||||
});
|
||||
};
|
||||
const commentLeft = (queue: GQLMODERATION_QUEUE, comment: GQLComment) => {
|
||||
subscriptionHandler.dispatch<
|
||||
SubscriptionToCommentEnteredModerationQueueResolver
|
||||
>("commentLeftModerationQueue", variables => {
|
||||
if (variables.queue && variables.queue !== queue) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
queue,
|
||||
comment,
|
||||
};
|
||||
});
|
||||
};
|
||||
await act(async () => {
|
||||
await wait(() =>
|
||||
expect(subscriptionHandler.has("commentEnteredModerationQueue")).toBe(
|
||||
true
|
||||
)
|
||||
);
|
||||
verifyCount(GQLMODERATION_QUEUE.REPORTED, 0);
|
||||
verifyCount(GQLMODERATION_QUEUE.PENDING, 0);
|
||||
verifyCount(GQLMODERATION_QUEUE.UNMODERATED, 0);
|
||||
|
||||
commentEntered(GQLMODERATION_QUEUE.REPORTED, reportedComments[0]);
|
||||
|
||||
verifyCount(GQLMODERATION_QUEUE.REPORTED, 1);
|
||||
verifyCount(GQLMODERATION_QUEUE.PENDING, 0);
|
||||
verifyCount(GQLMODERATION_QUEUE.UNMODERATED, 0);
|
||||
|
||||
commentEntered(GQLMODERATION_QUEUE.REPORTED, reportedComments[1]);
|
||||
|
||||
verifyCount(GQLMODERATION_QUEUE.REPORTED, 2);
|
||||
verifyCount(GQLMODERATION_QUEUE.PENDING, 0);
|
||||
verifyCount(GQLMODERATION_QUEUE.UNMODERATED, 0);
|
||||
|
||||
commentEntered(GQLMODERATION_QUEUE.PENDING, reportedComments[2]);
|
||||
commentEntered(GQLMODERATION_QUEUE.PENDING, reportedComments[2]);
|
||||
commentLeft(GQLMODERATION_QUEUE.REPORTED, reportedComments[1]);
|
||||
|
||||
verifyCount(GQLMODERATION_QUEUE.REPORTED, 1);
|
||||
verifyCount(GQLMODERATION_QUEUE.PENDING, 2);
|
||||
verifyCount(GQLMODERATION_QUEUE.UNMODERATED, 0);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,554 @@
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import {
|
||||
GQLCOMMENT_STATUS,
|
||||
GQLResolver,
|
||||
ModerationQueueToCommentsResolver,
|
||||
MutationToApproveCommentResolver,
|
||||
MutationToRejectCommentResolver,
|
||||
} from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createMutationResolverStub,
|
||||
createQueryResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
toJSON,
|
||||
waitForElement,
|
||||
waitUntilThrow,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
emptyRejectedComments,
|
||||
reportedComments,
|
||||
settings,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
});
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context, subscriptionHandler };
|
||||
}
|
||||
|
||||
it("renders empty reported queue", async () => {
|
||||
await act(async () => {
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
const { getByText } = within(testRenderer.root);
|
||||
await waitForElement(() => getByText("no more reported", { exact: false }));
|
||||
});
|
||||
});
|
||||
|
||||
it("renders empty pending queue", async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate/pending");
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
const { getByText } = within(testRenderer.root);
|
||||
await waitForElement(() => getByText("no more pending", { exact: false }));
|
||||
});
|
||||
|
||||
it("renders empty unmoderated queue", async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate/unmoderated");
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
const { getByText } = within(testRenderer.root);
|
||||
await waitForElement(() =>
|
||||
getByText("comments have been moderated", { exact: false })
|
||||
);
|
||||
});
|
||||
|
||||
it("renders empty rejected queue", async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate/rejected");
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
const { getByText } = within(testRenderer.root);
|
||||
await waitForElement(() =>
|
||||
getByText("no rejected comments", { exact: false })
|
||||
);
|
||||
});
|
||||
|
||||
it("renders reported queue with comments", async () => {
|
||||
await act(async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () =>
|
||||
pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<
|
||||
ModerationQueueToCommentsResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}) as any,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
await waitForElement(() => getByTestID("moderate-container"));
|
||||
expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders reported queue with comments", async () => {
|
||||
await act(async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () =>
|
||||
pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<
|
||||
ModerationQueueToCommentsResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
await waitForElement(() => getByTestID("moderate-container"));
|
||||
expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
it("show details of comment with flags", async () => {
|
||||
await act(async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () =>
|
||||
pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 1,
|
||||
comments: createQueryResolverStub<
|
||||
ModerationQueueToCommentsResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[0].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
const reported = await waitForElement(() =>
|
||||
getByTestID(`moderate-comment-${reportedComments[0].id}`)
|
||||
);
|
||||
expect(
|
||||
within(reported).queryByText(
|
||||
reportedComments[0].flags.nodes[0].additionalDetails!
|
||||
)
|
||||
).toBeNull();
|
||||
within(reported)
|
||||
.getByText("Details", { selector: "button" })
|
||||
.props.onClick();
|
||||
within(reported).getByText(
|
||||
reportedComments[0].flags.nodes[0].additionalDetails!
|
||||
);
|
||||
});
|
||||
});
|
||||
it("shows a moderate story", async () => {
|
||||
await act(async () => {
|
||||
const {
|
||||
testRenderer,
|
||||
context: { transitionControl },
|
||||
} = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () =>
|
||||
pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<
|
||||
ModerationQueueToCommentsResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}) as any,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const moderateStory = await waitForElement(
|
||||
() => within(testRenderer.root).getAllByText("Moderate Story")[0]
|
||||
);
|
||||
transitionControl.allowTransition = false;
|
||||
moderateStory.props.onClick({});
|
||||
// Expect a routing request was made to the right url.
|
||||
expect(transitionControl.history[0].pathname).toBe(
|
||||
`/admin/moderate/${reportedComments[0].story.id}`
|
||||
);
|
||||
});
|
||||
});
|
||||
it("renders reported queue with comments and load more", async () => {
|
||||
await act(async () => {
|
||||
const moderationQueuesStub = pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<ModerationQueueToCommentsResolver>(
|
||||
({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
};
|
||||
default:
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 10,
|
||||
after: reportedComments[1].createdAt,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[2],
|
||||
cursor: reportedComments[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
) as any,
|
||||
},
|
||||
});
|
||||
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () => moderationQueuesStub,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const moderateContainer = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("moderate-container")
|
||||
);
|
||||
|
||||
const { getByText, getAllByTestID, getByTestID } = within(
|
||||
moderateContainer
|
||||
);
|
||||
|
||||
// Get previous count of comments.
|
||||
const previousCount = getAllByTestID(/^moderate-comment-.*$/).length;
|
||||
|
||||
const loadMore = await waitForElement(() => getByText("Load More"));
|
||||
loadMore.props.onClick();
|
||||
|
||||
// Wait for load more to disappear.
|
||||
await waitUntilThrow(() => getByText("Load More"));
|
||||
|
||||
// Verify we have one more item now.
|
||||
const comments = getAllByTestID(/^moderate-comment-.*$/);
|
||||
expect(comments.length).toBe(previousCount + 1);
|
||||
|
||||
// Verify last one added was our new one
|
||||
expect(comments[comments.length - 1].props["data-testid"]).toBe(
|
||||
`moderate-comment-${reportedComments[2].id}`
|
||||
);
|
||||
|
||||
// Snapshot of added comment.
|
||||
expect(
|
||||
toJSON(getByTestID(`moderate-comment-${reportedComments[2].id}`))
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("approves comment in reported queue", async () => {
|
||||
await act(async () => {
|
||||
const approveCommentStub = createMutationResolverStub<
|
||||
MutationToApproveCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: reportedComments[0].id,
|
||||
commentRevisionID: reportedComments[0].revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: reportedComments[0].id,
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "mod-action",
|
||||
author: {
|
||||
id: viewer.id,
|
||||
username: viewer.username,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
moderationQueues: pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 1,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const moderationQueuesStub = pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<ModerationQueueToCommentsResolver>(
|
||||
({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
) as any,
|
||||
},
|
||||
});
|
||||
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () => moderationQueuesStub,
|
||||
},
|
||||
Mutation: {
|
||||
approveComment: approveCommentStub,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const testID = `moderate-comment-${reportedComments[0].id}`;
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
const comment = await waitForElement(() => getByTestID(testID));
|
||||
|
||||
const ApproveButton = await waitForElement(() =>
|
||||
within(comment).getByLabelText("Approve")
|
||||
);
|
||||
ApproveButton.props.onClick();
|
||||
|
||||
// Snapshot dangling state of comment.
|
||||
expect(toJSON(comment)).toMatchSnapshot("dangling");
|
||||
|
||||
// Wait until comment is gone.
|
||||
await waitUntilThrow(() => getByTestID(testID));
|
||||
|
||||
expect(approveCommentStub.called).toBe(true);
|
||||
|
||||
// Count should have been updated.
|
||||
expect(
|
||||
toJSON(getByTestID("moderate-navigation-reported-count"))
|
||||
).toMatchSnapshot("count should be 1");
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects comment in reported queue", async () => {
|
||||
await act(async () => {
|
||||
const rejectCommentStub = createMutationResolverStub<
|
||||
MutationToRejectCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: reportedComments[0].id,
|
||||
commentRevisionID: reportedComments[0].revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: reportedComments[0].id,
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "mod-action",
|
||||
author: {
|
||||
id: viewer.id,
|
||||
username: viewer.username,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
moderationQueues: pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 1,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () =>
|
||||
pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<
|
||||
ModerationQueueToCommentsResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}) as any,
|
||||
},
|
||||
}),
|
||||
},
|
||||
Mutation: {
|
||||
rejectComment: rejectCommentStub,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const testID = `moderate-comment-${reportedComments[0].id}`;
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
const comment = await waitForElement(() => getByTestID(testID));
|
||||
|
||||
const RejectButton = await waitForElement(() =>
|
||||
within(comment).getByLabelText("Reject")
|
||||
);
|
||||
RejectButton.props.onClick();
|
||||
|
||||
// Snapshot dangling state of comment.
|
||||
expect(toJSON(comment)).toMatchSnapshot("dangling");
|
||||
|
||||
// Wait until comment is gone.
|
||||
await waitUntilThrow(() => getByTestID(testID));
|
||||
|
||||
expect(rejectCommentStub.called).toBe(true);
|
||||
|
||||
// Count should have been updated.
|
||||
expect(
|
||||
toJSON(getByTestID("moderate-navigation-reported-count"))
|
||||
).toMatchSnapshot("count should be 1");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,139 @@
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import {
|
||||
GQLMODERATION_QUEUE,
|
||||
GQLResolver,
|
||||
SubscriptionToCommentEnteredModerationQueueResolver,
|
||||
SubscriptionToCommentLeftModerationQueueResolver,
|
||||
} from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
emptyRejectedComments,
|
||||
reportedComments,
|
||||
settings,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation(`http://localhost/admin/moderate/reported`);
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const container = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("moderate-main-container")
|
||||
);
|
||||
|
||||
return { testRenderer, context, container, subscriptionHandler };
|
||||
}
|
||||
|
||||
it("allows viewing new when new comments come in", async () => {
|
||||
const { subscriptionHandler, container } = await createTestRenderer();
|
||||
const commentData = reportedComments[0];
|
||||
expect(subscriptionHandler.has("commentEnteredModerationQueue")).toBe(true);
|
||||
|
||||
subscriptionHandler.dispatch<
|
||||
SubscriptionToCommentEnteredModerationQueueResolver
|
||||
>("commentEnteredModerationQueue", variables => {
|
||||
if (
|
||||
variables.storyID !== null ||
|
||||
variables.queue !== GQLMODERATION_QUEUE.REPORTED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
queue: GQLMODERATION_QUEUE.REPORTED,
|
||||
comment: commentData,
|
||||
};
|
||||
});
|
||||
|
||||
const viewNewButton = await waitForElement(() =>
|
||||
within(container).getByText("View 1 new", {
|
||||
exact: false,
|
||||
selector: "button",
|
||||
})
|
||||
);
|
||||
act(() => {
|
||||
viewNewButton.props.onClick();
|
||||
});
|
||||
// View New Button should disappear.
|
||||
expect(() => within(container).getByText(/View \d+ new/)).toThrow();
|
||||
// New comment should appear.
|
||||
within(container).getByTestID(`moderate-comment-${commentData.id}`);
|
||||
});
|
||||
|
||||
it("recognizes when same comment enters and leaves again", async () => {
|
||||
const { subscriptionHandler, container } = await createTestRenderer();
|
||||
const commentData = reportedComments[0];
|
||||
expect(subscriptionHandler.has("commentEnteredModerationQueue")).toBe(true);
|
||||
|
||||
subscriptionHandler.dispatch<
|
||||
SubscriptionToCommentEnteredModerationQueueResolver
|
||||
>("commentEnteredModerationQueue", variables => {
|
||||
if (
|
||||
variables.storyID !== null ||
|
||||
variables.queue !== GQLMODERATION_QUEUE.REPORTED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
queue: GQLMODERATION_QUEUE.REPORTED,
|
||||
comment: commentData,
|
||||
};
|
||||
});
|
||||
|
||||
await waitForElement(() =>
|
||||
within(container).getByText(/View \d+ new/, {
|
||||
exact: false,
|
||||
selector: "button",
|
||||
})
|
||||
);
|
||||
|
||||
subscriptionHandler.dispatch<
|
||||
SubscriptionToCommentLeftModerationQueueResolver
|
||||
>("commentLeftModerationQueue", variables => {
|
||||
if (
|
||||
variables.storyID !== null ||
|
||||
variables.queue !== GQLMODERATION_QUEUE.REPORTED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
queue: GQLMODERATION_QUEUE.REPORTED,
|
||||
comment: commentData,
|
||||
};
|
||||
});
|
||||
|
||||
// View New Button should disappear.
|
||||
expect(() => within(container).getByText(/View \d+ new/)).toThrow();
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import {
|
||||
GQLCOMMENT_STATUS,
|
||||
GQLMODERATION_QUEUE,
|
||||
GQLResolver,
|
||||
ModerationQueueToCommentsResolver,
|
||||
SubscriptionToCommentLeftModerationQueueResolver,
|
||||
} from "coral-framework/schema";
|
||||
import {
|
||||
createQueryResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
emptyRejectedComments,
|
||||
reportedComments,
|
||||
settings,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
const commentData = reportedComments[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation(`http://localhost/admin/moderate/reported`);
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
moderationQueues: () =>
|
||||
pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 1,
|
||||
comments: createQueryResolverStub<
|
||||
ModerationQueueToCommentsResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: commentData,
|
||||
cursor: commentData.createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: commentData.createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
}),
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const container = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("moderate-main-container")
|
||||
);
|
||||
|
||||
const comment = within(container).getByTestID(
|
||||
`moderate-comment-${commentData.id}`
|
||||
);
|
||||
|
||||
return { testRenderer, context, container, comment, subscriptionHandler };
|
||||
}
|
||||
|
||||
it("update comment status live", async () => {
|
||||
const { subscriptionHandler, comment } = await createTestRenderer();
|
||||
expect(subscriptionHandler.has("commentLeftModerationQueue")).toBe(true);
|
||||
expect(() =>
|
||||
within(comment).getByText("Moderated By", { exact: false })
|
||||
).toThrow();
|
||||
|
||||
subscriptionHandler.dispatch<
|
||||
SubscriptionToCommentLeftModerationQueueResolver
|
||||
>("commentLeftModerationQueue", variables => {
|
||||
if (
|
||||
variables.storyID !== null ||
|
||||
variables.queue !== GQLMODERATION_QUEUE.REPORTED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
queue: GQLMODERATION_QUEUE.REPORTED,
|
||||
comment: pureMerge<typeof commentData>(commentData, {
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "mod-action-1",
|
||||
moderator: users.moderators[0],
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
await waitForElement(() =>
|
||||
within(comment).getByText("Moderated By", { exact: false })
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,314 @@
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import {
|
||||
GQLCOMMENT_STATUS,
|
||||
GQLResolver,
|
||||
MutationToApproveCommentResolver,
|
||||
} from "coral-framework/schema";
|
||||
import {
|
||||
createMutationResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
toJSON,
|
||||
waitForElement,
|
||||
waitUntilThrow,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
emptyRejectedComments,
|
||||
rejectedComments,
|
||||
reportedComments,
|
||||
settings,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
});
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context, subscriptionHandler };
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
replaceHistoryLocation(`http://localhost/admin/moderate/rejected`);
|
||||
});
|
||||
|
||||
it("renders rejected queue with comments", async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
comments: ({ variables }) => {
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
storyID: null,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
await waitForElement(() => getByTestID("moderate-container"));
|
||||
expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows a moderate story", async () => {
|
||||
const {
|
||||
testRenderer,
|
||||
context: { transitionControl },
|
||||
} = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
comments: ({ variables }) => {
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
storyID: null,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const moderateStory = await waitForElement(
|
||||
() => within(testRenderer.root).getAllByText("Moderate Story")[0]
|
||||
);
|
||||
transitionControl.allowTransition = false;
|
||||
moderateStory.props.onClick({});
|
||||
// Expect a routing request was made to the right url.
|
||||
expect(transitionControl.history[0].pathname).toBe(
|
||||
`/admin/moderate/${reportedComments[0].story.id}`
|
||||
);
|
||||
});
|
||||
|
||||
it("renders rejected queue with comments and load more", async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
comments: ({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 5,
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
storyID: null,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
};
|
||||
default:
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 10,
|
||||
after: rejectedComments[1].createdAt,
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
storyID: null,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[2],
|
||||
cursor: rejectedComments[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const moderateContainer = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("moderate-container")
|
||||
);
|
||||
|
||||
const { getByText, getAllByTestID, getByTestID } = within(moderateContainer);
|
||||
|
||||
// Get previous count of comments.
|
||||
const previousCount = getAllByTestID(/^moderate-comment-.*$/).length;
|
||||
|
||||
const loadMore = await waitForElement(() => getByText("Load More"));
|
||||
loadMore.props.onClick();
|
||||
|
||||
// Wait for load more to disappear.
|
||||
await waitUntilThrow(() => getByText("Load More"));
|
||||
|
||||
// Verify we have one more item now.
|
||||
const comments = getAllByTestID(/^moderate-comment-.*$/);
|
||||
expect(comments.length).toBe(previousCount + 1);
|
||||
|
||||
// Verify last one added was our new one
|
||||
expect(comments[comments.length - 1].props["data-testid"]).toBe(
|
||||
`moderate-comment-${rejectedComments[2].id}`
|
||||
);
|
||||
|
||||
// Snapshot of added comment.
|
||||
expect(
|
||||
toJSON(getByTestID(`moderate-comment-${rejectedComments[2].id}`))
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("approves comment in rejected queue", async () => {
|
||||
const approveCommentStub = createMutationResolverStub<
|
||||
MutationToApproveCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: rejectedComments[0].id,
|
||||
commentRevisionID: rejectedComments[0].revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: rejectedComments[0].id,
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "mod-action",
|
||||
author: {
|
||||
id: viewer.id,
|
||||
username: viewer.username,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
moderationQueues: pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 1,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
comments: ({ variables }) => {
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
storyID: null,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
approveComment: approveCommentStub,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const testID = `moderate-comment-${rejectedComments[0].id}`;
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
const comment = await waitForElement(() => getByTestID(testID));
|
||||
|
||||
const ApproveButton = await waitForElement(() =>
|
||||
within(comment).getByLabelText("Approve")
|
||||
);
|
||||
ApproveButton.props.onClick();
|
||||
|
||||
// Snapshot dangling state of comment.
|
||||
expect(toJSON(getByTestID(testID))).toMatchSnapshot("dangling");
|
||||
|
||||
// Wait until comment is gone.
|
||||
await waitUntilThrow(() => getByTestID(testID));
|
||||
|
||||
expect(approveCommentStub.called).toBe(true);
|
||||
|
||||
// Count should have been updated.
|
||||
expect(
|
||||
toJSON(getByTestID("moderate-navigation-reported-count"))
|
||||
).toMatchSnapshot("count should be 1");
|
||||
});
|
||||
@@ -0,0 +1,264 @@
|
||||
import { noop } from "lodash";
|
||||
import { ReactTestInstance, ReactTestRenderer } from "react-test-renderer";
|
||||
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
findParentWithType,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
emptyRejectedComments,
|
||||
emptyStories,
|
||||
settings,
|
||||
stories,
|
||||
storyConnection,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
});
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context, subscriptionHandler };
|
||||
}
|
||||
|
||||
const openSearchBar = async (testRenderer: ReactTestRenderer) => {
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("moderate-searchBar-container")
|
||||
);
|
||||
});
|
||||
const searchBar = within(testRenderer.root).getByTestID(
|
||||
"moderate-searchBar-container"
|
||||
);
|
||||
const textField = within(searchBar).getByLabelText(
|
||||
"Search or jump to story..."
|
||||
);
|
||||
const form = findParentWithType(textField, "form")!;
|
||||
act(() => textField.props.onFocus({}));
|
||||
return { searchBar, textField, form };
|
||||
};
|
||||
|
||||
describe("all stories", () => {
|
||||
it("renders search bar", async () => {
|
||||
let searchBar: ReactTestInstance;
|
||||
await act(async () => {
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
searchBar = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("moderate-searchBar-container")
|
||||
);
|
||||
});
|
||||
expect(within(searchBar!).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("active", () => {
|
||||
it("search with no results", async () => {
|
||||
const query = "InterestingStory";
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
stories: ({ variables }) => {
|
||||
expectAndFail(variables.query).toBe(query);
|
||||
return emptyStories;
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const { searchBar, textField, form } = await openSearchBar(testRenderer);
|
||||
expect(within(searchBar).toJSON()).toMatchSnapshot();
|
||||
|
||||
await act(async () => {
|
||||
// Search for sth.
|
||||
textField.props.onChange(query);
|
||||
form.props.onSubmit();
|
||||
|
||||
// Ensure no results message is shown.
|
||||
await wait(() =>
|
||||
within(searchBar).getByText("No results", { exact: false })
|
||||
);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
// Blurring should close the listbox.
|
||||
textField.props.onBlur({});
|
||||
});
|
||||
expect(within(searchBar).queryByText("No results")).toBeNull();
|
||||
});
|
||||
it("search with actual results", async () => {
|
||||
const query = "InterestingStory";
|
||||
const {
|
||||
testRenderer,
|
||||
context: { transitionControl },
|
||||
} = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
stories: ({ variables }) => {
|
||||
expectAndFail(variables.query).toBe(query);
|
||||
return storyConnection;
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
transitionControl.allowTransition = false;
|
||||
const { searchBar, textField, form } = await openSearchBar(testRenderer);
|
||||
|
||||
const story = storyConnection.edges[0].node;
|
||||
|
||||
let storyOption: ReactTestInstance;
|
||||
|
||||
await act(async () => {
|
||||
// Search for sth.
|
||||
textField.props.onChange(query);
|
||||
form.props.onSubmit();
|
||||
|
||||
// Find the story in the search results.
|
||||
storyOption = findParentWithType(
|
||||
await waitForElement(() =>
|
||||
within(searchBar).getByText(story.metadata!.title!, {
|
||||
exact: false,
|
||||
})
|
||||
),
|
||||
"li"
|
||||
)!;
|
||||
});
|
||||
|
||||
// Go to story.
|
||||
storyOption!.props.onClick({ button: 0, preventDefault: noop });
|
||||
|
||||
// Expect a routing request was made to the right url.
|
||||
expect(transitionControl.history[0].pathname).toBe(
|
||||
`/admin/moderate/${story.id}`
|
||||
);
|
||||
});
|
||||
it("search with too many results", async () => {
|
||||
const query = "InterestingStory";
|
||||
const {
|
||||
testRenderer,
|
||||
context: { transitionControl },
|
||||
} = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
stories: ({ variables }) => {
|
||||
expectAndFail(variables.query).toBe(query);
|
||||
return pureMerge<typeof storyConnection>(storyConnection, {
|
||||
pageInfo: { hasNextPage: true },
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
transitionControl.allowTransition = false;
|
||||
const { searchBar, textField, form } = await openSearchBar(testRenderer);
|
||||
|
||||
let seeAllOption: ReactTestInstance;
|
||||
await act(async () => {
|
||||
// Search for sth.
|
||||
textField.props.onChange(query);
|
||||
form.props.onSubmit();
|
||||
|
||||
// Find see all options in the search results.
|
||||
seeAllOption = findParentWithType(
|
||||
await waitForElement(() =>
|
||||
within(searchBar).getByText("See all results", { exact: false })
|
||||
),
|
||||
"li"
|
||||
)!;
|
||||
});
|
||||
|
||||
expect(within(seeAllOption!).toJSON()).toMatchSnapshot();
|
||||
|
||||
// Go to story.
|
||||
seeAllOption!.props.onClick({ button: 0, preventDefault: noop });
|
||||
|
||||
// Expect a routing request was made to the right url.
|
||||
expect(transitionControl.history[0].pathname).toBe("/admin/stories");
|
||||
expect(transitionControl.history[0].search).toBe(`?q=${query}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("specified story", () => {
|
||||
beforeEach(() => {
|
||||
replaceHistoryLocation(`http://localhost/admin/moderate/${stories[0].id}`);
|
||||
});
|
||||
it("renders search bar", async () => {
|
||||
await act(async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
story: () => stories[0],
|
||||
},
|
||||
}),
|
||||
});
|
||||
const searchBar = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("moderate-searchBar-container")
|
||||
);
|
||||
const textField = within(searchBar).getByLabelText(
|
||||
"Search or jump to story..."
|
||||
);
|
||||
expect(textField.props.placeholder).toBe(stories[0].metadata!.title);
|
||||
});
|
||||
});
|
||||
it("shows moderate all option", async () => {
|
||||
const {
|
||||
testRenderer,
|
||||
context: { transitionControl },
|
||||
} = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
story: () => stories[0],
|
||||
},
|
||||
}),
|
||||
});
|
||||
transitionControl.allowTransition = false;
|
||||
const { searchBar } = await openSearchBar(testRenderer);
|
||||
|
||||
// Find see all options in the search results.
|
||||
const moderateAllOptions = findParentWithType(
|
||||
await waitForElement(() =>
|
||||
within(searchBar).getByText("Moderate all", { exact: false })
|
||||
),
|
||||
"li"
|
||||
)!;
|
||||
|
||||
// Activate moderate all.
|
||||
moderateAllOptions.props.onClick({ button: 0, preventDefault: noop });
|
||||
|
||||
// Expect a routing request was made to the right url.
|
||||
expect(transitionControl.history[0].pathname).toBe("/admin/moderate");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,189 @@
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import {
|
||||
GQLCOMMENT_STATUS,
|
||||
MutationToApproveCommentResolver,
|
||||
MutationToRejectCommentResolver,
|
||||
QueryToCommentResolver,
|
||||
} from "coral-framework/schema";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
createMutationResolverStub,
|
||||
createQueryResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
toJSON,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
rejectedComments,
|
||||
reportedComments,
|
||||
settings,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
});
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context, subscriptionHandler };
|
||||
}
|
||||
|
||||
const comment = rejectedComments[0];
|
||||
const commentStub = createQueryResolverStub<QueryToCommentResolver>(
|
||||
({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ id: comment.id });
|
||||
return reportedComments[0];
|
||||
}
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
replaceHistoryLocation(
|
||||
`http://localhost/admin/moderate/comment/${comment.id}`
|
||||
);
|
||||
});
|
||||
|
||||
it("renders single comment view", async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: {
|
||||
Query: {
|
||||
comment: commentStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
const container = await waitForElement(() =>
|
||||
getByTestID("single-moderate-container")
|
||||
);
|
||||
expect(toJSON(container)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("approves single comment", async () => {
|
||||
const approveCommentStub = createMutationResolverStub<
|
||||
MutationToApproveCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: comment.id,
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "mod-action",
|
||||
author: {
|
||||
id: viewer.id,
|
||||
username: viewer.username,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
moderationQueues: emptyModerationQueues,
|
||||
};
|
||||
});
|
||||
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: {
|
||||
Query: {
|
||||
comment: commentStub,
|
||||
},
|
||||
Mutation: {
|
||||
approveComment: approveCommentStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { getByLabelText, getByTestID } = within(testRenderer.root);
|
||||
const ApproveButton = await waitForElement(() => getByLabelText("Approve"));
|
||||
ApproveButton.props.onClick();
|
||||
|
||||
expect(
|
||||
toJSON(getByTestID(`moderate-comment-${comment.id}`))
|
||||
).toMatchSnapshot();
|
||||
|
||||
expect(approveCommentStub.called).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects single comment", async () => {
|
||||
const rejectCommentStub = createMutationResolverStub<
|
||||
MutationToRejectCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: comment.id,
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "mod-action",
|
||||
author: {
|
||||
id: viewer.id,
|
||||
username: viewer.username,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
moderationQueues: emptyModerationQueues,
|
||||
};
|
||||
});
|
||||
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: {
|
||||
Query: {
|
||||
comment: commentStub,
|
||||
},
|
||||
Mutation: {
|
||||
rejectComment: rejectCommentStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { getByLabelText, getByTestID } = within(testRenderer.root);
|
||||
const RejectButton = await waitForElement(() => getByLabelText("Reject"));
|
||||
RejectButton.props.onClick();
|
||||
|
||||
expect(
|
||||
toJSON(getByTestID(`moderate-comment-${comment.id}`))
|
||||
).toMatchSnapshot();
|
||||
expect(rejectCommentStub.called).toBe(true);
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import {
|
||||
GQLCOMMENT_STATUS,
|
||||
GQLResolver,
|
||||
SubscriptionToCommentStatusUpdatedResolver,
|
||||
} from "coral-framework/schema";
|
||||
import {
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import { reportedComments, settings, users } from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
const commentData = reportedComments[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
});
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation(
|
||||
`http://localhost/admin/moderate/comment/${commentData.id}`
|
||||
);
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
comment: () => commentData,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const container = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("single-moderate-container")
|
||||
);
|
||||
|
||||
const comment = within(container).getByTestID(
|
||||
`moderate-comment-${commentData.id}`
|
||||
);
|
||||
|
||||
return { testRenderer, context, container, comment, subscriptionHandler };
|
||||
}
|
||||
|
||||
it("update comment status live", async () => {
|
||||
const { subscriptionHandler, comment } = await createTestRenderer();
|
||||
expect(subscriptionHandler.has("commentStatusUpdated")).toBe(true);
|
||||
expect(() =>
|
||||
within(comment).getByText("Moderated By", { exact: false })
|
||||
).toThrow();
|
||||
|
||||
subscriptionHandler.dispatch<SubscriptionToCommentStatusUpdatedResolver>(
|
||||
"commentStatusUpdated",
|
||||
variables => {
|
||||
if (variables.id !== commentData.id) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
newStatus: GQLCOMMENT_STATUS.APPROVED,
|
||||
comment: pureMerge<typeof commentData>(commentData, {
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "mod-action-1",
|
||||
moderator: users.moderators[0],
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// When status was changed by another user, the moderated by info should appear.
|
||||
await waitForElement(() =>
|
||||
within(comment).getByText("Moderated By", { exact: false })
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
emptyRejectedComments,
|
||||
settings,
|
||||
stories,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
});
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context, subscriptionHandler };
|
||||
}
|
||||
|
||||
it("passes storyID to the endpoints", async () => {
|
||||
replaceHistoryLocation(`http://localhost/admin/moderate/${stories[0].id}`);
|
||||
await act(async () => {
|
||||
await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: ({ variables }) => {
|
||||
expectAndFail(variables.storyID).toBe(stories[0].id);
|
||||
return emptyModerationQueues;
|
||||
},
|
||||
comments: ({ variables }) => {
|
||||
expectAndFail(variables.storyID).toBe(stories[0].id);
|
||||
return emptyRejectedComments;
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
toJSON,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
emptyRejectedComments,
|
||||
settings,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
});
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context, subscriptionHandler } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context, subscriptionHandler };
|
||||
}
|
||||
|
||||
describe("tab bar", () => {
|
||||
it("renders tab bar (empty queues)", async () => {
|
||||
await act(async () => {
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
await waitForElement(() => getByTestID("moderate-container"));
|
||||
expect(
|
||||
toJSON(getByTestID("moderate-tabBar-container"))
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
it("should not show moderate story link in comment cards", async () => {
|
||||
await act(async () => {
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
await waitForElement(() => getByTestID("moderate-container"));
|
||||
expect(
|
||||
within(testRenderer.root).queryByText("Moderate Story")
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user