[CORL-218] In Stream Approve and Reject (#2340)

* feat: instream moderation

* fix: tests

* test: add tests for in stream moderation

* fix: lint

* fix: snapshots
This commit is contained in:
Kiwi
2019-06-04 19:27:15 +02:00
committed by GitHub
parent 1cf2f61bfb
commit 593d94ddbb
74 changed files with 1106 additions and 297 deletions
@@ -22,10 +22,10 @@ const UserBoxAuthenticated: FunctionComponent<
<Flex itemGutter="half" wrap>
<Localized
id="general-userBoxAuthenticated-signedInAs"
username={<Username />}
Username={<Username />}
>
<Typography variant="bodyCopy" container="div">
{"Signed in as <username></username>."}
{"Signed in as <Username></Username>."}
</Typography>
</Localized>
{props.showLogoutButton && (
@@ -6,14 +6,14 @@ exports[`renders correctly with logout button 1`] = `
wrap={true}
>
<Localized
Username={<Username />}
id="general-userBoxAuthenticated-signedInAs"
username={<Username />}
>
<ForwardRef(forwardRef)
container="div"
variant="bodyCopy"
>
Signed in as &lt;username&gt;&lt;/username&gt;.
Signed in as &lt;Username&gt;&lt;/Username&gt;.
</ForwardRef(forwardRef)>
</Localized>
<Localized
@@ -48,14 +48,14 @@ exports[`renders correctly without logout button 1`] = `
wrap={true}
>
<Localized
Username={<Username />}
id="general-userBoxAuthenticated-signedInAs"
username={<Username />}
>
<ForwardRef(forwardRef)
container="div"
variant="bodyCopy"
>
Signed in as &lt;username&gt;&lt;/username&gt;.
Signed in as &lt;Username&gt;&lt;/Username&gt;.
</ForwardRef(forwardRef)>
</Localized>
</ForwardRef(forwardRef)>
+15 -6
View File
@@ -1,23 +1,29 @@
enum View {
enum VIEW {
SIGN_UP
SIGN_IN
FORGOT_PASSWORD
}
enum Tab {
enum TAB {
COMMENTS
PROFILE
}
enum ProfileTab {
enum PROFILE_TAB {
MY_COMMENTS
SETTINGS
}
enum COMMENT_VIEWER_ACTION {
EDIT
ACCEPT
REJECT
}
type AuthPopup {
open: Boolean!
focus: Boolean!
view: View
view: VIEW
}
extend type Comment {
@@ -26,6 +32,9 @@ extend type Comment {
# localReplies contains only comments created by the user
# on the ultimate threading level.
localReplies: [Comment!]
# Remember last viewer action that could have caused a status change.
lastViewerAction: COMMENT_VIEWER_ACTION
}
type Local {
@@ -33,13 +42,13 @@ type Local {
accessTokenExp: Int
accessTokenJTI: String
loggedIn: Boolean!
activeTab: Tab!
activeTab: TAB!
authPopup: AuthPopup!
storyID: String
storyURL: String
commentID: String
defaultStreamOrderBy: COMMENT_SORT!
profileTab: ProfileTab!
profileTab: PROFILE_TAB!
bla: [String]
}
@@ -37,6 +37,7 @@ function createDefaultProps(add: DeepPartial<Props> = {}): Props {
},
pending: false,
tags: [],
lastViewerAction: null,
},
settings: {
disableCommenting: {
@@ -3,9 +3,9 @@ import React, { Component, MouseEvent } from "react";
import { graphql } from "react-relay";
import { isBeforeDate } from "coral-common/utils";
import { getURLWithCommentID } from "coral-framework/helpers";
import { getURLWithCommentID, roleIsAtLeast } from "coral-framework/helpers";
import withFragmentContainer from "coral-framework/lib/relay/withFragmentContainer";
import { GQLUSER_STATUS } from "coral-framework/schema";
import { GQLUSER_ROLE, GQLUSER_STATUS } from "coral-framework/schema";
import { PropTypesOf } from "coral-framework/types";
import { CommentContainer_comment as CommentData } from "coral-stream/__generated__/CommentContainer_comment.graphql";
import { CommentContainer_settings as SettingsData } from "coral-stream/__generated__/CommentContainer_settings.graphql";
@@ -23,6 +23,9 @@ import { isCommentVisible } from "../helpers";
import ButtonsBar from "./ButtonsBar";
import EditCommentFormContainer from "./EditCommentForm";
import IndentedComment from "./IndentedComment";
import CaretContainer, {
RejectedTombstoneContainer,
} from "./ModerationDropdown";
import PermalinkButtonContainer from "./PermalinkButton";
import ReactionButtonContainer from "./ReactionButton";
import ReplyButton from "./ReplyButton";
@@ -158,6 +161,9 @@ export class CommentContainer extends Component<Props, State> {
this.props.viewer &&
this.props.viewer.status.current.includes(GQLUSER_STATUS.BANNED)
);
const showCaret =
this.props.viewer &&
roleIsAtLeast(this.props.viewer.role, GQLUSER_ROLE.MODERATOR);
if (showEditDialog) {
return (
<div data-testid={`comment-${comment.id}`}>
@@ -170,10 +176,15 @@ export class CommentContainer extends Component<Props, State> {
</div>
);
}
// Comment is not visible, so don't render it.
// This is the case when the comment was just edited and
// the comment status has changed.
if (!isCommentVisible(comment)) {
// Comment is not visible after viewer rejected it.
if (
comment.lastViewerAction === "REJECT" &&
comment.status === "REJECTED"
) {
return <RejectedTombstoneContainer comment={comment} />;
}
// Comment is not visible after edit, so don't render it anymore.
if (comment.lastViewerAction === "EDIT" && !isCommentVisible(comment)) {
return null;
}
return (
@@ -201,18 +212,20 @@ export class CommentContainer extends Component<Props, State> {
}
tags={comment.tags.map(t => t.name)}
topBarRight={
(editable && (
<Localized id="comments-commentContainer-editButton">
<Button
color="primary"
variant="underlined"
onClick={this.openEditDialog}
>
Edit
</Button>
</Localized>
)) ||
undefined
<Flex itemGutter>
{editable && (
<Localized id="comments-commentContainer-editButton">
<Button
color="primary"
variant="underlined"
onClick={this.openEditDialog}
>
Edit
</Button>
</Localized>
)}
{showCaret && <CaretContainer comment={comment} />}
</Flex>
}
footer={
<>
@@ -292,6 +305,7 @@ const enhanced = withSetCommentIDMutation(
ignoredUsers {
id
}
role
...UsernameWithPopoverContainer_viewer
...ReactionButtonContainer_viewer
...ReportButtonContainer_viewer
@@ -330,10 +344,13 @@ const enhanced = withSetCommentIDMutation(
name
}
pending
lastViewerAction
...ReplyCommentFormContainer_comment
...EditCommentFormContainer_comment
...ReactionButtonContainer_comment
...ReportButtonContainer_comment
...CaretContainer_comment
...RejectedTombstoneContainer_comment
}
`,
settings: graphql`
@@ -51,7 +51,10 @@ function commit(environment: Environment, input: EditCommentInput) {
},
clientMutationId: (clientMutationId++).toString(),
},
} as any, // TODO: (cvle) generated types should contain one for the optimistic response.
},
updater: store => {
store.get(input.commentID)!.setValue("EDIT", "lastViewerAction");
},
});
}
@@ -19,13 +19,13 @@ const InReplyTo: FunctionComponent<Props> = ({ username }) => {
return (
<Flex alignItems="center">
<Icon className={styles.icon}>reply</Icon>{" "}
<Localized id="comments-inReplyTo" username={<Username />}>
<Localized id="comments-inReplyTo" Username={<Username />}>
<Typography
variant="timestamp"
container="span"
className={styles.inReplyTo}
>
{"In reply to <username><username>"}
{"In reply to <Username></Username>"}
</Typography>
</Localized>
</Flex>
@@ -0,0 +1,46 @@
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
import {
commitMutationPromiseNormalized,
createMutation,
MutationInput,
} from "coral-framework/lib/relay";
import { AcceptCommentMutation as MutationTypes } from "coral-stream/__generated__/AcceptCommentMutation.graphql";
let clientMutationId = 0;
const AcceptCommentMutation = createMutation(
"acceptComment",
(environment: Environment, input: MutationInput<MutationTypes>) =>
commitMutationPromiseNormalized<MutationTypes>(environment, {
mutation: graphql`
mutation AcceptCommentMutation($input: AcceptCommentInput!) {
acceptComment(input: $input) {
comment {
status
}
clientMutationId
}
}
`,
optimisticResponse: {
acceptComment: {
comment: {
status: "ACCEPTED",
},
},
},
variables: {
input: {
...input,
clientMutationId: (clientMutationId++).toString(),
},
},
updater: store => {
store.get(input.commentID)!.setValue("ACCEPT", "lastViewerAction");
},
})
);
export default AcceptCommentMutation;
@@ -0,0 +1,3 @@
.root {
padding: 2px 4px;
}
@@ -0,0 +1,69 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { CaretContainer_comment as CommentData } from "coral-stream/__generated__/CaretContainer_comment.graphql";
import { Button, ClickOutside, Icon, Popover } from "coral-ui/components";
import ModerationDropdownContainer from "./ModerationDropdownContainer";
import styles from "./CaretContainer.css";
interface Props {
comment: CommentData;
}
const CaretContainer: FunctionComponent<Props> = props => {
const popoverID = `comments-moderationMenu`;
return (
<Localized
id="comments-moderationDropdown-popover"
attrs={{ description: true }}
>
<Popover
id={popoverID}
placement="bottom-end"
description="A popover menu to moderate the comment"
body={({ toggleVisibility }) => (
<ClickOutside onClickOutside={toggleVisibility}>
<ModerationDropdownContainer
comment={props.comment}
onDismiss={toggleVisibility}
/>
</ClickOutside>
)}
>
{({ toggleVisibility, visible, ref }) => (
<Localized
id="comments-moderationDropdown-caretButton"
attrs={{ "aria-label": true }}
>
<Button
variant="ghost"
size="small"
className={styles.root}
onClick={toggleVisibility}
aria-controls={popoverID}
active={visible}
ref={ref}
aria-label="Moderate"
>
<Icon>{visible ? "expand_less" : "expand_more"}</Icon>
</Button>
</Localized>
)}
</Popover>
</Localized>
);
};
const enhanced = withFragmentContainer<Props>({
comment: graphql`
fragment CaretContainer_comment on Comment {
...ModerationDropdownContainer_comment
}
`,
})(CaretContainer);
export default enhanced;
@@ -0,0 +1,8 @@
.approved {
color: var(--palette-success-dark);
font-weight: var(--font-weight-bold);
}
.rejected {
color: var(--palette-error-dark);
font-weight: var(--font-weight-bold);
}
@@ -0,0 +1,104 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, useCallback } from "react";
import { graphql } from "react-relay";
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
import { ModerationDropdownContainer_comment as CommentData } from "coral-stream/__generated__/ModerationDropdownContainer_comment.graphql";
import {
Dropdown,
DropdownButton,
DropdownDivider,
Icon,
} from "coral-ui/components";
import AcceptCommentMutation from "./AcceptCommentMutation";
import styles from "./ModerationDropdownContainer.css";
import RejectCommentMutation from "./RejectCommentMutation";
interface Props {
comment: CommentData;
onDismiss: () => void;
}
const ModerationDropdownContainer: FunctionComponent<Props> = ({
comment,
onDismiss,
}) => {
const accept = useMutation(AcceptCommentMutation);
const reject = useMutation(RejectCommentMutation);
const onAccept = useCallback(() => {
accept({ commentID: comment.id, commentRevisionID: comment.revision.id });
}, [accept, comment]);
const onReject = useCallback(
() =>
reject({ commentID: comment.id, commentRevisionID: comment.revision.id }),
[accept, comment]
);
const accepted = comment.status === "ACCEPTED";
const rejected = comment.status === "REJECTED";
return (
<Dropdown>
{accepted ? (
<Localized id="comments-moderationDropdown-approved">
<DropdownButton
icon={<Icon className={styles.approved}>check</Icon>}
className={styles.approved}
disabled
>
Approved
</DropdownButton>
</Localized>
) : (
<Localized id="comments-moderationDropdown-approve">
<DropdownButton icon={<Icon>check</Icon>} onClick={onAccept}>
Approve
</DropdownButton>
</Localized>
)}
{rejected ? (
<Localized id="comments-moderationDropdown-rejected">
<DropdownButton
icon={<Icon className={styles.rejected}>close</Icon>}
className={styles.rejected}
disabled
>
Rejected
</DropdownButton>
</Localized>
) : (
<Localized id="comments-moderationDropdown-reject">
<DropdownButton icon={<Icon>close</Icon>} onClick={onReject}>
Reject
</DropdownButton>
</Localized>
)}
<DropdownDivider />
<Localized id="comments-moderationDropdown-goToModerate">
<DropdownButton
href={`/admin/moderate/comment/${comment.id}`}
target="_blank"
anchor
>
Go to Moderate
</DropdownButton>
</Localized>
</Dropdown>
);
};
const enhanced = withFragmentContainer<Props>({
comment: graphql`
fragment ModerationDropdownContainer_comment on Comment {
id
revision {
id
}
status
}
`,
})(ModerationDropdownContainer);
export default enhanced;
@@ -0,0 +1,46 @@
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
import {
commitMutationPromiseNormalized,
createMutation,
MutationInput,
} from "coral-framework/lib/relay";
import { RejectCommentMutation as MutationTypes } from "coral-stream/__generated__/RejectCommentMutation.graphql";
let clientMutationId = 0;
const RejectCommentMutation = createMutation(
"rejectComment",
(environment: Environment, input: MutationInput<MutationTypes>) =>
commitMutationPromiseNormalized<MutationTypes>(environment, {
mutation: graphql`
mutation RejectCommentMutation($input: RejectCommentInput!) {
rejectComment(input: $input) {
comment {
status
}
clientMutationId
}
}
`,
optimisticResponse: {
rejectComment: {
comment: {
status: "REJECTED",
},
},
},
variables: {
input: {
...input,
clientMutationId: (clientMutationId++).toString(),
},
},
updater: store => {
store.get(input.commentID)!.setValue("REJECT", "lastViewerAction");
},
})
);
export default RejectCommentMutation;
@@ -0,0 +1,42 @@
import React, { FunctionComponent, useMemo } from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { RejectedTombstoneContainer_comment as CommentData } from "coral-stream/__generated__/RejectedTombstoneContainer_comment.graphql";
import { CallOut, TextLink } from "coral-ui/components";
import { Localized } from "fluent-react/compat";
interface Props {
comment: CommentData;
}
const RejectedTombstoneContainer: FunctionComponent<Props> = ({ comment }) => {
const Link = useMemo<React.FunctionComponent>(
() => ({ children }) => (
<TextLink href={`/admin/moderate/comment/${comment.id}`} target="_blank">
{children}
</TextLink>
),
[comment]
);
return (
<CallOut color="primary" fullWidth>
<Localized id="comments-rejectedTombstone" TextLink={<Link />}>
<div>
You have rejected this comment.{" "}
<Link>Go to Moderate to review this decision.</Link>
</div>
</Localized>
</CallOut>
);
};
const enhanced = withFragmentContainer<Props>({
comment: graphql`
fragment RejectedTombstoneContainer_comment on Comment {
id
}
`,
})(RejectedTombstoneContainer);
export default enhanced;
@@ -0,0 +1,4 @@
export { default, default as CaretContainer } from "./CaretContainer";
export {
default as RejectedTombstoneContainer,
} from "./RejectedTombstoneContainer";
@@ -52,10 +52,10 @@ function commit(environment: Environment, input: CreateCommentReactionInput) {
total: currentCount + 1,
},
},
},
} as any,
clientMutationId: (clientMutationId++).toString(),
},
} as any, // TODO: (cvle) generated types should contain one for the optimistic response.
},
});
}
@@ -51,10 +51,10 @@ function commit(environment: Environment, input: RemoveCommentReactionInput) {
total: currentCount - 1,
},
},
},
} as any,
clientMutationId: clientMutationId.toString(),
},
} as any, // TODO: (cvle) generated types should contain one for the optimistic response.
},
});
}
@@ -187,7 +187,7 @@ function commit(
},
clientMutationId: (clientMutationId++).toString(),
},
} as any, // TODO: (cvle) generated types should contain one for the optimistic response.
},
optimisticUpdater: store => {
// Skip optimistic update if comment is probably premoderated.
if (expectPremoderation) {
@@ -19,9 +19,9 @@ const ReplyTo: FunctionComponent<Props> = ({ username }) => {
return (
<Flex alignItems="center" className={styles.root}>
<Icon>reply</Icon>{" "}
<Localized id="comments-replyTo" username={<Username />}>
<Localized id="comments-replyTo" Username={<Username />}>
<Typography container="span" className={styles.text}>
{"Replying to: <username></username>"}
{"Replying to: <Username></Username>"}
</Typography>
</Localized>
</Flex>
@@ -10,14 +10,14 @@ exports[`renders correctly 1`] = `
</ForwardRef(forwardRef)>
<Localized
Username={<Username />}
id="comments-replyTo"
username={<Username />}
>
<ForwardRef(forwardRef)
className="ReplyTo-text"
container="span"
>
Replying to: &lt;username&gt;&lt;/username&gt;
Replying to: &lt;Username&gt;&lt;/Username&gt;
</ForwardRef(forwardRef)>
</Localized>
</ForwardRef(forwardRef)>
@@ -29,6 +29,7 @@ exports[`hide reply button 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -70,6 +71,7 @@ exports[`hide reply button 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -86,6 +88,11 @@ exports[`hide reply button 1`] = `
parentAuthorName={null}
showEditedMarker={false}
tags={Array []}
topBarRight={
<ForwardRef(forwardRef)
itemGutter={true}
/>
}
username={
<Relay(UsernameWithPopoverContainer)
user={
@@ -131,6 +138,7 @@ exports[`renders body only 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -178,6 +186,7 @@ exports[`renders body only 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -194,6 +203,11 @@ exports[`renders body only 1`] = `
parentAuthorName={null}
showEditedMarker={false}
tags={Array []}
topBarRight={
<ForwardRef(forwardRef)
itemGutter={true}
/>
}
username={
<Relay(UsernameWithPopoverContainer)
user={
@@ -239,6 +253,7 @@ exports[`renders disabled reply when commenting has been disabled 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -286,6 +301,7 @@ exports[`renders disabled reply when commenting has been disabled 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -302,6 +318,11 @@ exports[`renders disabled reply when commenting has been disabled 1`] = `
parentAuthorName={null}
showEditedMarker={false}
tags={Array []}
topBarRight={
<ForwardRef(forwardRef)
itemGutter={true}
/>
}
username={
<Relay(UsernameWithPopoverContainer)
user={
@@ -347,6 +368,7 @@ exports[`renders disabled reply when story is closed 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -394,6 +416,7 @@ exports[`renders disabled reply when story is closed 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -410,6 +433,11 @@ exports[`renders disabled reply when story is closed 1`] = `
parentAuthorName={null}
showEditedMarker={false}
tags={Array []}
topBarRight={
<ForwardRef(forwardRef)
itemGutter={true}
/>
}
username={
<Relay(UsernameWithPopoverContainer)
user={
@@ -455,6 +483,7 @@ exports[`renders in reply to 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": Object {
"author": Object {
"username": "ParentAuthor",
@@ -506,6 +535,7 @@ exports[`renders in reply to 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": Object {
"author": Object {
"username": "ParentAuthor",
@@ -526,6 +556,11 @@ exports[`renders in reply to 1`] = `
parentAuthorName="ParentAuthor"
showEditedMarker={false}
tags={Array []}
topBarRight={
<ForwardRef(forwardRef)
itemGutter={true}
/>
}
username={
<Relay(UsernameWithPopoverContainer)
user={
@@ -571,6 +606,7 @@ exports[`renders staff badge 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -622,6 +658,7 @@ exports[`renders staff badge 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -646,6 +683,11 @@ exports[`renders staff badge 1`] = `
"Staff",
]
}
topBarRight={
<ForwardRef(forwardRef)
itemGutter={true}
/>
}
username={
<Relay(UsernameWithPopoverContainer)
user={
@@ -691,6 +733,7 @@ exports[`renders username and body 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -738,6 +781,7 @@ exports[`renders username and body 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -754,6 +798,11 @@ exports[`renders username and body 1`] = `
parentAuthorName={null}
showEditedMarker={false}
tags={Array []}
topBarRight={
<ForwardRef(forwardRef)
itemGutter={true}
/>
}
username={
<Relay(UsernameWithPopoverContainer)
user={
@@ -799,6 +848,7 @@ exports[`shows conversation link 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -846,6 +896,7 @@ exports[`shows conversation link 1`] = `
"edited": false,
},
"id": "comment-id",
"lastViewerAction": null,
"parent": null,
"pending": false,
"status": "NONE",
@@ -867,6 +918,11 @@ exports[`shows conversation link 1`] = `
parentAuthorName={null}
showEditedMarker={false}
tags={Array []}
topBarRight={
<ForwardRef(forwardRef)
itemGutter={true}
/>
}
username={
<Relay(UsernameWithPopoverContainer)
user={
@@ -11,15 +11,15 @@ exports[`renders correctly 1`] = `
</ForwardRef(forwardRef)>
<Localized
Username={<Username />}
id="comments-inReplyTo"
username={<Username />}
>
<ForwardRef(forwardRef)
className="InReplyTo-inReplyTo"
container="span"
variant="timestamp"
>
In reply to &lt;username&gt;&lt;username&gt;
In reply to &lt;Username&gt;&lt;/Username&gt;
</ForwardRef(forwardRef)>
</Localized>
</ForwardRef(forwardRef)>
@@ -8,8 +8,8 @@ import React, {
import { usePrevious } from "coral-framework/hooks";
import { graphql, withFragmentContainer } from "coral-framework/lib/relay";
import { TombstoneOrHideContainer_comment as CommentData } from "coral-stream/__generated__/TombstoneOrHideContainer_comment.graphql";
import { TombstoneOrHideContainer_viewer as ViewerData } from "coral-stream/__generated__/TombstoneOrHideContainer_viewer.graphql";
import { IgnoredTombstoneOrHideContainer_comment as CommentData } from "coral-stream/__generated__/IgnoredTombstoneOrHideContainer_comment.graphql";
import { IgnoredTombstoneOrHideContainer_viewer as ViewerData } from "coral-stream/__generated__/IgnoredTombstoneOrHideContainer_viewer.graphql";
import { CallOut } from "coral-ui/components";
interface Props {
@@ -37,7 +37,7 @@ const useTombstone = (hide: boolean) => {
return tombstone;
};
const TombstoneOrHideContainer: FunctionComponent<Props> = ({
const IgnoredTombstoneOrHideContainer: FunctionComponent<Props> = ({
viewer,
comment,
children,
@@ -70,20 +70,20 @@ const TombstoneOrHideContainer: FunctionComponent<Props> = ({
const enhanced = withFragmentContainer<Props>({
viewer: graphql`
fragment TombstoneOrHideContainer_viewer on User {
fragment IgnoredTombstoneOrHideContainer_viewer on User {
ignoredUsers {
id
}
}
`,
comment: graphql`
fragment TombstoneOrHideContainer_comment on Comment {
fragment IgnoredTombstoneOrHideContainer_comment on Comment {
author {
id
username
}
}
`,
})(TombstoneOrHideContainer);
})(IgnoredTombstoneOrHideContainer);
export default enhanced;
@@ -47,7 +47,7 @@ const enhanced = withFragmentContainer<Props>({
viewer: graphql`
fragment LocalReplyListContainer_viewer on User {
...CommentContainer_viewer
...TombstoneOrHideContainer_viewer
...IgnoredTombstoneOrHideContainer_viewer
}
`,
story: graphql`
@@ -61,7 +61,7 @@ const enhanced = withFragmentContainer<Props>({
localReplies {
id
...CommentContainer_comment
...TombstoneOrHideContainer_comment
...IgnoredTombstoneOrHideContainer_comment
}
}
`,
@@ -6,13 +6,13 @@ import { PropTypesOf } from "coral-framework/types";
import { Button, HorizontalGutter } from "coral-ui/components";
import CommentContainer from "../Comment";
import IgnoredTombstoneOrHideContainer from "../IgnoredTombstoneOrHideContainer";
import Indent from "../Indent";
import TombstoneOrHideContainer from "../TombstoneOrHideContainer";
export interface ReplyListProps {
story: PropTypesOf<typeof CommentContainer>["story"];
viewer: PropTypesOf<typeof CommentContainer>["viewer"] &
PropTypesOf<typeof TombstoneOrHideContainer>["viewer"];
PropTypesOf<typeof IgnoredTombstoneOrHideContainer>["viewer"];
comment: {
id: string;
};
@@ -22,7 +22,7 @@ export interface ReplyListProps {
replyListElement?: React.ReactElement<any>;
showConversationLink?: boolean;
} & PropTypesOf<typeof CommentContainer>["comment"] &
PropTypesOf<typeof TombstoneOrHideContainer>["comment"]
PropTypesOf<typeof IgnoredTombstoneOrHideContainer>["comment"]
>;
settings: PropTypesOf<typeof CommentContainer>["settings"];
onShowAll?: () => void;
@@ -41,7 +41,7 @@ const ReplyList: FunctionComponent<ReplyListProps> = props => {
role="log"
>
{props.comments.map(comment => (
<TombstoneOrHideContainer
<IgnoredTombstoneOrHideContainer
key={comment.id}
viewer={props.viewer}
comment={comment}
@@ -60,7 +60,7 @@ const ReplyList: FunctionComponent<ReplyListProps> = props => {
/>
{comment.replyListElement}
</HorizontalGutter>
</TombstoneOrHideContainer>
</IgnoredTombstoneOrHideContainer>
))}
{props.hasMore && (
<Indent level={props.indentLevel} noBorder>
@@ -22,6 +22,7 @@ it("renders correctly", () => {
replies: {
edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }],
},
lastViewerAction: null,
},
settings: {
reaction: {
@@ -51,6 +52,7 @@ it("renders correctly when replies are empty", () => {
id: "comment-id",
status: "NONE",
replies: { edges: [] },
lastViewerAction: null,
},
relay: {
hasMore: noop,
@@ -83,6 +85,7 @@ describe("when has more replies", () => {
replies: {
edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }],
},
lastViewerAction: null,
},
settings: {
reaction: {
@@ -53,27 +53,25 @@ export class ReplyListContainer extends React.Component<Props> {
) {
return null;
}
// Don't render replies if comment is not visible.
// We have this comment here because it's
// status just changed due to an edit.
// We keep it around for the CommentContainer to show
// some information to the user.
const comments = !isCommentVisible(this.props.comment)
? []
: this.props.comment.replies.edges.map(edge => ({
...edge.node,
replyListElement: this.props.ReplyListComponent && (
<this.props.ReplyListComponent
viewer={this.props.viewer}
comment={edge.node}
story={this.props.story}
settings={this.props.settings}
/>
),
// ReplyListContainer5 contains replyCount.
showConversationLink:
((edge.node as any) as ReplyNode5).replyCount > 0,
}));
const comments =
// Comment is not visible after a viewer action, so don't render it anymore.
this.props.comment.lastViewerAction &&
!isCommentVisible(this.props.comment)
? []
: this.props.comment.replies.edges.map(edge => ({
...edge.node,
replyListElement: this.props.ReplyListComponent && (
<this.props.ReplyListComponent
viewer={this.props.viewer}
comment={edge.node}
story={this.props.story}
settings={this.props.settings}
/>
),
// ReplyListContainer5 contains replyCount.
showConversationLink:
((edge.node as any) as ReplyNode5).replyCount > 0,
}));
return (
<ReplyList
viewer={this.props.viewer}
@@ -164,7 +162,7 @@ const ReplyListContainer5 = createReplyListContainer(
viewer: graphql`
fragment ReplyListContainer5_viewer on User {
...CommentContainer_viewer
...TombstoneOrHideContainer_viewer
...IgnoredTombstoneOrHideContainer_viewer
...LocalReplyListContainer_viewer
}
`,
@@ -189,6 +187,7 @@ const ReplyListContainer5 = createReplyListContainer(
) {
id
status
lastViewerAction
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ReplyList_replies") {
edges {
@@ -196,7 +195,7 @@ const ReplyListContainer5 = createReplyListContainer(
id
replyCount
...CommentContainer_comment
...TombstoneOrHideContainer_comment
...IgnoredTombstoneOrHideContainer_comment
...LocalReplyListContainer_comment
}
}
@@ -230,7 +229,7 @@ const ReplyListContainer4 = createReplyListContainer(
fragment ReplyListContainer4_viewer on User {
...ReplyListContainer5_viewer
...CommentContainer_viewer
...TombstoneOrHideContainer_viewer
...IgnoredTombstoneOrHideContainer_viewer
}
`,
settings: graphql`
@@ -254,13 +253,14 @@ const ReplyListContainer4 = createReplyListContainer(
) {
id
status
lastViewerAction
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ReplyList_replies") {
edges {
node {
id
...CommentContainer_comment
...TombstoneOrHideContainer_comment
...IgnoredTombstoneOrHideContainer_comment
...ReplyListContainer5_comment
}
}
@@ -293,7 +293,7 @@ const ReplyListContainer3 = createReplyListContainer(
fragment ReplyListContainer3_viewer on User {
...ReplyListContainer4_viewer
...CommentContainer_viewer
...TombstoneOrHideContainer_viewer
...IgnoredTombstoneOrHideContainer_viewer
}
`,
settings: graphql`
@@ -317,13 +317,14 @@ const ReplyListContainer3 = createReplyListContainer(
) {
id
status
lastViewerAction
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ReplyList_replies") {
edges {
node {
id
...CommentContainer_comment
...TombstoneOrHideContainer_comment
...IgnoredTombstoneOrHideContainer_comment
...ReplyListContainer4_comment
}
}
@@ -356,7 +357,7 @@ const ReplyListContainer2 = createReplyListContainer(
fragment ReplyListContainer2_viewer on User {
...ReplyListContainer3_viewer
...CommentContainer_viewer
...TombstoneOrHideContainer_viewer
...IgnoredTombstoneOrHideContainer_viewer
}
`,
settings: graphql`
@@ -380,13 +381,14 @@ const ReplyListContainer2 = createReplyListContainer(
) {
id
status
lastViewerAction
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ReplyList_replies") {
edges {
node {
id
...CommentContainer_comment
...TombstoneOrHideContainer_comment
...IgnoredTombstoneOrHideContainer_comment
...ReplyListContainer3_comment
}
}
@@ -419,7 +421,7 @@ const ReplyListContainer1 = createReplyListContainer(
fragment ReplyListContainer1_viewer on User {
...ReplyListContainer2_viewer
...CommentContainer_viewer
...TombstoneOrHideContainer_viewer
...IgnoredTombstoneOrHideContainer_viewer
}
`,
settings: graphql`
@@ -443,13 +445,14 @@ const ReplyListContainer1 = createReplyListContainer(
) {
id
status
lastViewerAction
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ReplyList_replies") {
edges {
node {
id
...CommentContainer_comment
...TombstoneOrHideContainer_comment
...IgnoredTombstoneOrHideContainer_comment
...ReplyListContainer2_comment
}
}
@@ -6,7 +6,7 @@ exports[`renders correctly 1`] = `
id="coral-comments-replyList-log--comment-id"
role="log"
>
<Relay(TombstoneOrHideContainer)
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-1",
@@ -45,8 +45,8 @@ exports[`renders correctly 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
<Relay(TombstoneOrHideContainer)
</Relay(IgnoredTombstoneOrHideContainer)>
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-2",
@@ -87,7 +87,7 @@ exports[`renders correctly 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
</Relay(IgnoredTombstoneOrHideContainer)>
</ForwardRef(forwardRef)>
`;
@@ -97,7 +97,7 @@ exports[`when there is more disables load more button 1`] = `
id="coral-comments-replyList-log--comment-id"
role="log"
>
<Relay(TombstoneOrHideContainer)
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-1",
@@ -134,8 +134,8 @@ exports[`when there is more disables load more button 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
<Relay(TombstoneOrHideContainer)
</Relay(IgnoredTombstoneOrHideContainer)>
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-2",
@@ -172,7 +172,7 @@ exports[`when there is more disables load more button 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
</Relay(IgnoredTombstoneOrHideContainer)>
<Indent
level={1}
noBorder={true}
@@ -201,7 +201,7 @@ exports[`when there is more renders a load more button 1`] = `
id="coral-comments-replyList-log--comment-id"
role="log"
>
<Relay(TombstoneOrHideContainer)
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-1",
@@ -238,8 +238,8 @@ exports[`when there is more renders a load more button 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
<Relay(TombstoneOrHideContainer)
</Relay(IgnoredTombstoneOrHideContainer)>
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-2",
@@ -276,7 +276,7 @@ exports[`when there is more renders a load more button 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
</Relay(IgnoredTombstoneOrHideContainer)>
<Indent
level={1}
noBorder={true}
@@ -5,6 +5,7 @@ exports[`renders correctly 1`] = `
comment={
Object {
"id": "comment-id",
"lastViewerAction": null,
"replies": Object {
"edges": Array [
Object {
@@ -104,6 +105,7 @@ exports[`when has more replies renders hasMore 1`] = `
comment={
Object {
"id": "comment-id",
"lastViewerAction": null,
"replies": Object {
"edges": Array [
Object {
@@ -162,6 +164,7 @@ exports[`when has more replies when showing all disables show all button 1`] = `
comment={
Object {
"id": "comment-id",
"lastViewerAction": null,
"replies": Object {
"edges": Array [
Object {
@@ -220,6 +223,7 @@ exports[`when has more replies when showing all enable show all button after loa
comment={
Object {
"id": "comment-id",
"lastViewerAction": null,
"replies": Object {
"edges": Array [
Object {
@@ -159,7 +159,7 @@ function commit(
},
clientMutationId: (clientMutationId++).toString(),
},
} as any, // TODO: (cvle) generated types should contain one for the optimistic response.
},
optimisticUpdater: store => {
// Skip optimistic update if comment is probably premoderated.
if (expectPremoderation) {
@@ -7,8 +7,8 @@ import UserBoxContainer from "coral-stream/common/UserBox";
import { Button, Flex, HorizontalGutter, Spinner } from "coral-ui/components";
import CommentContainer from "../Comment";
import IgnoredTombstoneOrHideContainer from "../IgnoredTombstoneOrHideContainer";
import ReplyListContainer from "../ReplyList";
import TombstoneOrHideContainer from "../TombstoneOrHideContainer";
import BannedInfo from "./BannedInfo";
import CommunityGuidelinesContainer from "./CommunityGuidelines";
import PostCommentFormContainer from "./PostCommentForm";
@@ -35,7 +35,7 @@ export interface StreamProps {
comments: ReadonlyArray<
{ id: string } & PropTypesOf<typeof CommentContainer>["comment"] &
PropTypesOf<typeof ReplyListContainer>["comment"] &
PropTypesOf<typeof TombstoneOrHideContainer>["comment"]
PropTypesOf<typeof IgnoredTombstoneOrHideContainer>["comment"]
>;
onLoadMore?: () => void;
hasMore?: boolean;
@@ -44,7 +44,7 @@ export interface StreamProps {
| PropTypesOf<typeof UserBoxContainer>["viewer"] &
PropTypesOf<typeof CommentContainer>["viewer"] &
PropTypesOf<typeof ReplyListContainer>["viewer"] &
PropTypesOf<typeof TombstoneOrHideContainer>["viewer"]
PropTypesOf<typeof IgnoredTombstoneOrHideContainer>["viewer"]
| null;
orderBy: PropTypesOf<typeof SortMenu>["orderBy"];
onChangeOrderBy: (e: React.ChangeEvent<HTMLSelectElement>) => void;
@@ -84,7 +84,7 @@ const Stream: FunctionComponent<StreamProps> = props => {
aria-live="polite"
>
{props.comments.map(comment => (
<TombstoneOrHideContainer
<IgnoredTombstoneOrHideContainer
key={comment.id}
viewer={props.viewer}
comment={comment}
@@ -103,7 +103,7 @@ const Stream: FunctionComponent<StreamProps> = props => {
story={props.story}
/>
</HorizontalGutter>
</TombstoneOrHideContainer>
</IgnoredTombstoneOrHideContainer>
))}
{props.hasMore && (
<Localized id="comments-stream-loadMore">
@@ -127,7 +127,7 @@ const enhanced = withPaginationContainer<
edges {
node {
...StreamContainer_comment @relay(mask: false)
...TombstoneOrHideContainer_comment
...IgnoredTombstoneOrHideContainer_comment
}
}
}
@@ -146,7 +146,7 @@ const enhanced = withPaginationContainer<
...UserBoxContainer_viewer
...CreateCommentReplyMutation_viewer
...CreateCommentMutation_viewer
...TombstoneOrHideContainer_viewer
...IgnoredTombstoneOrHideContainer_viewer
status {
current
}
@@ -50,7 +50,7 @@ exports[`renders correctly 1`] = `
id="coral-comments-stream-log"
role="log"
>
<Relay(TombstoneOrHideContainer)
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-1",
@@ -103,8 +103,8 @@ exports[`renders correctly 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
<Relay(TombstoneOrHideContainer)
</Relay(IgnoredTombstoneOrHideContainer)>
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-2",
@@ -157,7 +157,7 @@ exports[`renders correctly 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
</Relay(IgnoredTombstoneOrHideContainer)>
</ForwardRef(forwardRef)>
</ForwardRef(forwardRef)>
`;
@@ -212,7 +212,7 @@ exports[`when there is more disables load more button 1`] = `
id="coral-comments-stream-log"
role="log"
>
<Relay(TombstoneOrHideContainer)
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-1",
@@ -265,8 +265,8 @@ exports[`when there is more disables load more button 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
<Relay(TombstoneOrHideContainer)
</Relay(IgnoredTombstoneOrHideContainer)>
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-2",
@@ -319,7 +319,7 @@ exports[`when there is more disables load more button 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
</Relay(IgnoredTombstoneOrHideContainer)>
<Localized
id="comments-stream-loadMore"
>
@@ -388,7 +388,7 @@ exports[`when there is more renders a load more button 1`] = `
id="coral-comments-stream-log"
role="log"
>
<Relay(TombstoneOrHideContainer)
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-1",
@@ -441,8 +441,8 @@ exports[`when there is more renders a load more button 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
<Relay(TombstoneOrHideContainer)
</Relay(IgnoredTombstoneOrHideContainer)>
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-2",
@@ -495,7 +495,7 @@ exports[`when there is more renders a load more button 1`] = `
viewer={null}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
</Relay(IgnoredTombstoneOrHideContainer)>
<Localized
id="comments-stream-loadMore"
>
@@ -564,7 +564,7 @@ exports[`when use is logged in renders correctly 1`] = `
id="coral-comments-stream-log"
role="log"
>
<Relay(TombstoneOrHideContainer)
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-1",
@@ -617,8 +617,8 @@ exports[`when use is logged in renders correctly 1`] = `
viewer={Object {}}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
<Relay(TombstoneOrHideContainer)
</Relay(IgnoredTombstoneOrHideContainer)>
<Relay(IgnoredTombstoneOrHideContainer)
comment={
Object {
"id": "comment-2",
@@ -671,7 +671,7 @@ exports[`when use is logged in renders correctly 1`] = `
viewer={Object {}}
/>
</ForwardRef(forwardRef)>
</Relay(TombstoneOrHideContainer)>
</Relay(IgnoredTombstoneOrHideContainer)>
</ForwardRef(forwardRef)>
</ForwardRef(forwardRef)>
`;
@@ -1,7 +1,7 @@
import React, { FunctionComponent, useCallback } from "react";
import { PropTypesOf } from "coral-framework/types";
import { ProfileLocal as Local } from "coral-stream/__generated__/ProfileLocal.graphql";
import { ProfileLocal } from "coral-stream/__generated__/ProfileLocal.graphql";
import UserBoxContainer from "coral-stream/common/UserBox";
import {
HorizontalGutter,
@@ -24,13 +24,13 @@ export interface ProfileProps {
}
const Profile: FunctionComponent<ProfileProps> = props => {
const [local, setLocal] = useLocal<Local>(graphql`
const [local, setLocal] = useLocal<ProfileLocal>(graphql`
fragment ProfileLocal on Local {
profileTab
}
`);
const onTabClick = useCallback(
(tab: Local["profileTab"]) => setLocal({ profileTab: tab }),
(tab: ProfileLocal["profileTab"]) => setLocal({ profileTab: tab }),
[setLocal]
);
return (
@@ -170,6 +170,11 @@ exports[`renders permalink view 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -371,6 +376,11 @@ exports[`renders permalink view 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -570,6 +580,11 @@ exports[`renders permalink view 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -765,6 +780,11 @@ exports[`renders permalink view 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -949,6 +969,11 @@ exports[`renders permalink view 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -204,6 +204,11 @@ exports[`renders conversation thread 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -418,6 +423,11 @@ exports[`shows more of this conversation 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -624,6 +634,11 @@ exports[`shows more of this conversation 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -823,6 +838,11 @@ exports[`shows more of this conversation 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -74,18 +74,22 @@ exports[`cancel edit 1`] = `
</div>
</div>
<div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
<div
className="Flex-root Flex-flex Flex-itemGutter"
>
Edit
</button>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Edit
</button>
</div>
</div>
</div>
<div
@@ -805,18 +809,22 @@ exports[`edit a comment: render comment with edit button 1`] = `
</div>
</div>
<div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
<div
className="Flex-root Flex-flex Flex-itemGutter"
>
Edit
</button>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Edit
</button>
</div>
</div>
</div>
<div
@@ -1029,18 +1037,22 @@ exports[`edit a comment: server response 1`] = `
</div>
</div>
<div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
<div
className="Flex-root Flex-flex Flex-itemGutter"
>
Edit
</button>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Edit
</button>
</div>
</div>
</div>
<div
@@ -1244,18 +1256,22 @@ exports[`shows expiry message: edit form closed 1`] = `
</div>
</div>
<div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
<div
className="Flex-root Flex-flex Flex-itemGutter"
>
Edit
</button>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Edit
</button>
</div>
</div>
</div>
<div
@@ -83,6 +83,11 @@ exports[`renders comment stream with load more button 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -267,6 +272,11 @@ exports[`renders comment stream with load more button 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -74,18 +74,22 @@ exports[`post a comment: optimistic response 1`] = `
</div>
</div>
<div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
<div
className="Flex-root Flex-flex Flex-itemGutter"
>
Edit
</button>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Edit
</button>
</div>
</div>
</div>
<div
@@ -73,6 +73,11 @@ exports[`post a reply: open reply form 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -455,18 +460,22 @@ exports[`post a reply: optimistic response 1`] = `
</div>
</div>
<div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
<div
className="Flex-root Flex-flex Flex-itemGutter"
>
Edit
</button>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Edit
</button>
</div>
</div>
</div>
<div
@@ -665,6 +674,11 @@ exports[`renders comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -872,6 +886,11 @@ exports[`renders comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -1079,6 +1098,11 @@ exports[`renders comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -1286,6 +1310,11 @@ exports[`renders comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -1493,6 +1522,11 @@ exports[`renders comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -1700,6 +1734,11 @@ exports[`renders comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -73,6 +73,11 @@ exports[`post a reply: open reply form 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -440,18 +445,22 @@ exports[`post a reply: optimistic response 1`] = `
</div>
</div>
<div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
<div
className="Flex-root Flex-flex Flex-itemGutter"
>
Edit
</button>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Edit
</button>
</div>
</div>
</div>
<div
@@ -295,6 +295,11 @@ exports[`renders comment stream with community guidelines 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -479,6 +484,11 @@ exports[`renders comment stream with community guidelines 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -82,6 +82,11 @@ exports[`renders reply list 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -271,6 +276,11 @@ exports[`renders reply list 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -455,6 +465,11 @@ exports[`renders reply list 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -641,6 +656,11 @@ exports[`renders reply list 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -313,6 +313,11 @@ exports[`renders comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -502,6 +507,11 @@ exports[`renders comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -82,6 +82,11 @@ exports[`renders comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -73,6 +73,11 @@ exports[`renders deepest comment with link 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -83,6 +83,11 @@ exports[`renders app with comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -267,6 +272,11 @@ exports[`renders app with comment stream 1`] = `
</time>
</div>
</div>
<div>
<div
className="Flex-root Flex-flex Flex-itemGutter"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
@@ -0,0 +1,135 @@
import { pureMerge } from "coral-common/utils";
import { GQLCOMMENT_STATUS, GQLResolver } from "coral-framework/schema";
import {
createResolversStub,
CreateTestRendererParams,
waitForElement,
within,
} from "coral-framework/testHelpers";
import { moderators, settings, stories } from "../../fixtures";
import create from "../create";
const story = stories[0];
const firstComment = story.comments.edges[0].node;
const viewer = moderators[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer, context } = create({
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
story: () => story,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(story.id, "storyID");
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const tabPane = await waitForElement(() =>
within(testRenderer.root).getByTestID("current-tab-pane")
);
return {
testRenderer,
context,
tabPane,
};
}
it("render go to moderate link", async () => {
const { testRenderer } = await createTestRenderer();
const comment = await waitForElement(() =>
within(testRenderer.root).getByTestID(`comment-${firstComment.id}`)
);
const caretButton = within(comment).getByLabelText("Moderate");
caretButton.props.onClick();
const link = within(comment).getByText("Go to Moderate", {
selector: "a",
exact: false,
});
expect(link.props.href).toBe(`/admin/moderate/comment/${firstComment.id}`);
});
it("accept comment", async () => {
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Mutation: {
acceptComment: ({ variables }) => {
expectAndFail(variables).toMatchObject({
commentID: firstComment.id,
commentRevisionID: firstComment.revision.id,
});
return {
comment: pureMerge<typeof firstComment>(firstComment, {
status: GQLCOMMENT_STATUS.ACCEPTED,
}),
};
},
},
}),
});
const comment = await waitForElement(() =>
within(testRenderer.root).getByTestID(`comment-${firstComment.id}`)
);
const caretButton = within(comment).getByLabelText("Moderate");
caretButton.props.onClick();
const approveButton = within(comment).getByText("Approve", {
selector: "button",
});
approveButton.props.onClick();
await waitForElement(() =>
within(comment).getByText("Approved", { exact: false })
);
});
it("reject comment", async () => {
const { testRenderer, tabPane } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Mutation: {
rejectComment: ({ variables }) => {
expectAndFail(variables).toMatchObject({
commentID: firstComment.id,
commentRevisionID: firstComment.revision.id,
});
return {
comment: pureMerge<typeof firstComment>(firstComment, {
status: GQLCOMMENT_STATUS.REJECTED,
}),
};
},
},
}),
});
const comment = await waitForElement(() =>
within(testRenderer.root).getByTestID(`comment-${firstComment.id}`)
);
const caretButton = within(comment).getByLabelText("Moderate");
caretButton.props.onClick();
const rejectButton = within(comment).getByText("Reject", {
selector: "button",
});
rejectButton.props.onClick();
await waitForElement(() =>
within(tabPane).getByText("You have rejected this comment", {
exact: false,
})
);
const link = within(tabPane).getByText(
"Go to Moderate to review this decision",
{ selector: "a", exact: false }
);
expect(link.props.href).toBe(`/admin/moderate/comment/${firstComment.id}`);
});