[next] Report Comment + Stream Refactor & Test Improvements (#2144)

* fix: stabilize mutations

* feat: Refactor Stream + Implement Report Comment

* test: add unit tests

* chore: Improve stream integration tests

* test: add integration test for reaction

* test: add feature/integration tests for report comment

* fix: order import

* fix: performance issues + keep active button styling until clicked away
This commit is contained in:
Kiwi
2019-01-16 22:31:43 +00:00
committed by Wyatt Johnson
parent 94eb72a9bf
commit 939152ee81
118 changed files with 7985 additions and 18846 deletions
+33 -18
View File
@@ -19555,9 +19555,9 @@
"dev": true
},
"popper.js": {
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.3.tgz",
"integrity": "sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU=",
"version": "1.14.6",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz",
"integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==",
"dev": true
},
"portfinder": {
@@ -19573,7 +19573,7 @@
"dependencies": {
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
}
@@ -23268,23 +23268,38 @@
"dev": true
},
"react-popper": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.0.0.tgz",
"integrity": "sha1-uZRSFE6P5KzHf6PZWajHngemUIQ=",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.2.tgz",
"integrity": "sha512-UbFWj55Yt9uqvy0oZ+vULDL2Bw1oxeZF9/JzGyxQ5ypgauRH/XlarA5+HLZWro/Zss6Ht2kqpegtb6sYL8GUGw==",
"dev": true,
"requires": {
"babel-runtime": "6.x.x",
"create-react-context": "^0.2.1",
"popper.js": "^1.14.1",
"@babel/runtime": "^7.1.2",
"create-react-context": "<=0.2.2",
"popper.js": "^1.14.4",
"prop-types": "^15.6.1",
"typed-styles": "^0.0.5",
"warning": "^3.0.0"
"typed-styles": "^0.0.7",
"warning": "^4.0.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz",
"integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.12.0"
}
},
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==",
"dev": true
},
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz",
"integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==",
"dev": true,
"requires": {
"loose-envify": "^1.0.0"
@@ -27160,9 +27175,9 @@
}
},
"typed-styles": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.5.tgz",
"integrity": "sha512-ht+rEe5UsdEBAa3gr64+QjUOqjOLJfWLvl5HZR5Ev9uo/OnD3p43wPeFSB1hNFc13GXQF/JU1Bn0YHLUqBRIlw==",
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
"integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==",
"dev": true
},
"typedarray": {
+1 -1
View File
@@ -256,7 +256,7 @@
"react-dev-utils": "6.0.0-next.3e165448",
"react-dom": "^16.5.2",
"react-final-form": "^3.6.4",
"react-popper": "^1.0.0",
"react-popper": "^1.3.2",
"react-relay": "^1.7.0-rc.1",
"react-responsive": "^5.0.0",
"react-test-renderer": "^16.5.2",
@@ -1,5 +1,4 @@
.popover {
z-index: var(--zindex-popover);
width: 350px;
}
+8 -2
View File
@@ -13,8 +13,14 @@ export const VALIDATION_REQUIRED = () => (
);
export const VALIDATION_TOO_SHORT = (minLength: number) => (
<Localized id="framework-validation-tooShort">
<span>{"This field must contain at least {$minLength} characters."}</span>
<Localized id="framework-validation-tooShort" $minLength={minLength}>
<span>{"Please enter at least {$minLength} characters."}</span>
</Localized>
);
export const VALIDATION_TOO_LONG = (maxLength: number) => (
<Localized id="framework-validation-tooLong" $maxLength={maxLength}>
<span>{"Please enter at max {$maxLength} characters."}</span>
</Localized>
);
@@ -19,6 +19,8 @@ import {
USERNAME_TOO_LONG,
USERNAME_TOO_SHORT,
VALIDATION_REQUIRED,
VALIDATION_TOO_LONG,
VALIDATION_TOO_SHORT,
} from "./messages";
export type Validator<T = any, V = any> = (v: T, values: V) => ReactNode;
@@ -76,6 +78,24 @@ export const validateURL = createValidator(
INVALID_URL()
);
/**
* validateMinLength is a Validator that checks that the field has a min length of characters
*/
export const validateMinLength = (minLength: number) =>
createValidator(
v => !v || v.length >= minLength,
VALIDATION_TOO_SHORT(minLength)
);
/**
* validateMaxLength is a Validator that checks that the field has max length of characters
*/
export const validateMaxLength = (maxLength: number) =>
createValidator(
v => !v || v.length <= maxLength,
VALIDATION_TOO_LONG(maxLength)
);
/**
* validateUsernameMinLength is a Validator that checks that the username has a min length of characters
*/
@@ -0,0 +1,36 @@
import { ReactTestInstance } from "react-test-renderer";
import matchText, { TextMatchOptions, TextMatchPattern } from "./matchText";
const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => (
i: ReactTestInstance
) => {
// Only look at dom components.
if (typeof i.type !== "string" || !i.props.id) {
return false;
}
return matchText(pattern, i.props.id, {
collapseWhitespace: false,
...options,
});
};
export function getByID(
container: ReactTestInstance,
pattern: TextMatchPattern,
options?: TextMatchOptions
) {
return container.find(matcher(pattern, options));
}
export function queryByID(
container: ReactTestInstance,
pattern: TextMatchPattern,
options?: TextMatchOptions
) {
const results = container.findAll(matcher(pattern, options));
if (!results.length) {
return null;
}
return results[0];
}
@@ -62,7 +62,7 @@ export function queryAllByLabelText(
options?: TextMatchOptions
) {
const matches = container.findAll(ariaLabelMatcher(pattern, options));
queryAllByText(container, pattern).forEach(i => {
queryAllByText(container, pattern, options).forEach(i => {
if (typeof i.type !== "string") {
return;
}
@@ -1,6 +1,8 @@
import React from "react";
import { ReactTestInstance } from "react-test-renderer";
import findParentsWithType from "./findParentsWithType";
import findParentWithType from "./findParentWithType";
import matchText, { TextMatchOptions, TextMatchPattern } from "./matchText";
const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => (
@@ -10,6 +12,12 @@ const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => (
if (typeof i.type !== "string") {
return false;
}
if (
i.props.dangerouslySetInnerHTML &&
matchText(pattern, i.props.dangerouslySetInnerHTML.__html, options)
) {
return true;
}
if (!i.props.children) {
return false;
}
@@ -22,20 +30,34 @@ const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => (
return false;
};
interface SelectorOptions {
selector?: string | React.ComponentClass<any> | React.StatelessComponent<any>;
}
export function getByText(
container: ReactTestInstance,
pattern: TextMatchPattern,
options?: TextMatchOptions
options?: TextMatchOptions & SelectorOptions
) {
return container.find(matcher(pattern, options));
const result = findParentWithType(
container.find(matcher(pattern, options)),
options && options.selector
);
if (!result) {
throw new Error(`Couldn't find text ${pattern}`);
}
return result;
}
export function getAllByText(
container: ReactTestInstance,
pattern: TextMatchPattern,
options?: TextMatchOptions
options?: TextMatchOptions & SelectorOptions
) {
const results = container.findAll(matcher(pattern, options));
const results = findParentsWithType(
container.findAll(matcher(pattern, options)),
options && options.selector
);
if (!results.length) {
throw new Error(`Couldn't find text ${pattern}`);
}
@@ -45,9 +67,12 @@ export function getAllByText(
export function queryByText(
container: ReactTestInstance,
pattern: TextMatchPattern,
options?: TextMatchOptions
options?: TextMatchOptions & SelectorOptions
) {
const results = container.findAll(matcher(pattern, options));
const results = findParentsWithType(
container.findAll(matcher(pattern, options)),
options && options.selector
);
if (!results.length) {
return null;
}
@@ -57,7 +82,10 @@ export function queryByText(
export function queryAllByText(
container: ReactTestInstance,
pattern: TextMatchPattern,
options?: TextMatchOptions
options?: TextMatchOptions & SelectorOptions
) {
return container.findAll(matcher(pattern, options));
return findParentsWithType(
container.findAll(matcher(pattern, options)),
options && options.selector
);
}
@@ -0,0 +1,20 @@
import { ReactTestInstance } from "react-test-renderer";
export default function findParentWithType(
instance: ReactTestInstance,
selector:
| string
| React.ComponentClass<any>
| React.StatelessComponent<any> = "*"
): ReactTestInstance | null {
if (selector === "*") {
return instance;
}
if (instance.type === selector) {
return instance;
}
if (instance.parent) {
return findParentWithType(instance.parent, selector);
}
return null;
}
@@ -0,0 +1,15 @@
import { ReactTestInstance } from "react-test-renderer";
import findParentWithType from "./findParentWithType";
export default function findParentsWithType(
instances: ReactTestInstance[],
selector:
| string
| React.ComponentClass<any>
| React.StatelessComponent<any> = "*"
): ReactTestInstance[] {
return instances
.map(i => findParentWithType(i, selector))
.filter(i => i) as ReactTestInstance[];
}
@@ -20,3 +20,5 @@ export { default as matchText } from "./matchText";
export { default as toJSON } from "./toJSON";
export { default as replaceHistoryLocation } from "./replaceHistoryLocation";
export { default as createAuthToken } from "./createAuthToken";
export { default as findParentsWithType } from "./findParentsWithType";
export { default as findParentWithType } from "./findParentWithType";
@@ -1,5 +1,9 @@
import { ReactTestInstance } from "react-test-renderer";
import {
getByID,
queryByID,
} from "./byID";
import {
getAllByLabelText,
getByLabelText,
@@ -47,6 +51,8 @@ export default function within(container: ReactTestInstance) {
getAllByTestID: applyContainer(container, getAllByTestID),
queryByTestID: applyContainer(container, queryByTestID),
queryAllByTestID: applyContainer(container, queryAllByTestID),
getByID: applyContainer(container, getByID),
queryByID: applyContainer(container, queryByID),
getByText: applyContainer(container, getByText),
getAllByText: applyContainer(container, getAllByText),
queryByText: applyContainer(container, queryByText),
+2 -2
View File
@@ -19,10 +19,10 @@ const App: StatelessComponent<AppProps> = props => {
<HorizontalGutter className={styles.root}>
<TabBarQuery />
<TabContent activeTab={props.activeTab} className={styles.tabContent}>
<TabPane tabId="COMMENTS">
<TabPane tabId="COMMENTS" data-testid="current-tab-pane">
<CommentsPaneContainer />
</TabPane>
<TabPane tabId="PROFILE">
<TabPane tabId="PROFILE" data-testid="current-tab-pane">
<ProfileQuery />
</TabPane>
</TabContent>
@@ -10,11 +10,13 @@ exports[`renders correctly 1`] = `
className="App-tabContent"
>
<TabPane
data-testid="current-tab-pane"
tabId="COMMENTS"
>
<withContext(withLocalStateContainer(CommentsPaneContainer)) />
</TabPane>
<TabPane
data-testid="current-tab-pane"
tabId="PROFILE"
>
<withContext(withLocalStateContainer(ProfileQuery)) />
@@ -0,0 +1,55 @@
import { pick } from "lodash";
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
import {
commitMutationPromiseNormalized,
createMutationContainer,
} from "talk-framework/lib/relay";
import { Omit } from "talk-framework/types";
import { CreateCommentDontAgreeMutation as MutationTypes } from "talk-stream/__generated__/CreateCommentDontAgreeMutation.graphql";
export type CreateCommentDontAgreeInput = Omit<
MutationTypes["variables"]["input"],
"clientMutationId"
>;
const mutation = graphql`
mutation CreateCommentDontAgreeMutation(
$input: CreateCommentDontAgreeInput!
) {
createCommentDontAgree(input: $input) {
comment {
id
myActionPresence {
dontAgree
}
}
clientMutationId
}
}
`;
let clientMutationId = 0;
function commit(environment: Environment, input: CreateCommentDontAgreeInput) {
return commitMutationPromiseNormalized<MutationTypes>(environment, {
mutation,
variables: {
input: {
...pick(input, ["commentID", "commentRevisionID", "additionalDetails"]),
clientMutationId: (clientMutationId++).toString(),
},
},
});
}
export const withCreateCommentDontAgreeMutation = createMutationContainer(
"createCommentDontAgree",
commit
);
export type CreateCommentDontAgreeMutation = (
input: CreateCommentDontAgreeInput
) => Promise<MutationTypes["response"]["createCommentDontAgree"]>;
@@ -0,0 +1,58 @@
import { pick } from "lodash";
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
import {
commitMutationPromiseNormalized,
createMutationContainer,
} from "talk-framework/lib/relay";
import { Omit } from "talk-framework/types";
import { CreateCommentFlagMutation as MutationTypes } from "talk-stream/__generated__/CreateCommentFlagMutation.graphql";
export type CreateCommentFlagInput = Omit<
MutationTypes["variables"]["input"],
"clientMutationId"
>;
const mutation = graphql`
mutation CreateCommentFlagMutation($input: CreateCommentFlagInput!) {
createCommentFlag(input: $input) {
comment {
id
myActionPresence {
flag
}
}
clientMutationId
}
}
`;
let clientMutationId = 0;
function commit(environment: Environment, input: CreateCommentFlagInput) {
return commitMutationPromiseNormalized<MutationTypes>(environment, {
mutation,
variables: {
input: {
...pick(input, [
"commentID",
"commentRevisionID",
"reason",
"additionalDetails",
]),
clientMutationId: (clientMutationId++).toString(),
},
},
});
}
export const withCreateCommentFlagMutation = createMutationContainer(
"createCommentFlag",
commit
);
export type CreateCommentFlagMutation = (
input: CreateCommentFlagInput
) => Promise<MutationTypes["response"]["createCommentFlag"]>;
@@ -1,3 +1,4 @@
import { pick } from "lodash";
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
@@ -37,7 +38,7 @@ function commit(environment: Environment, input: CreateCommentReactionInput) {
mutation,
variables: {
input: {
...input,
...pick(input, ["commentID", "commentRevisionID"]),
clientMutationId: clientMutationId.toString(),
},
},
@@ -1,3 +1,4 @@
import { pick } from "lodash";
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
@@ -38,7 +39,7 @@ function commit(environment: Environment, input: EditCommentInput) {
mutation,
variables: {
input: {
...input,
...pick(input, ["commentID", "body"]),
clientMutationId: clientMutationId.toString(),
},
},
@@ -1,3 +1,4 @@
import { pick } from "lodash";
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
@@ -36,7 +37,7 @@ function commit(environment: Environment, input: RemoveCommentReactionInput) {
mutation,
variables: {
input: {
...input,
...pick(input, ["commentID"]),
clientMutationId: (clientMutationId++).toString(),
},
},
+10
View File
@@ -3,6 +3,16 @@ export {
CreateCommentMutation,
CreateCommentInput,
} from "./CreateCommentMutation";
export {
withCreateCommentFlagMutation,
CreateCommentFlagMutation,
CreateCommentFlagInput,
} from "./CreateCommentFlagMutation";
export {
withCreateCommentDontAgreeMutation,
CreateCommentDontAgreeMutation,
CreateCommentDontAgreeInput,
} from "./CreateCommentDontAgreeMutation";
export {
withCreateCommentReplyMutation,
CreateCommentReplyMutation,
@@ -7,7 +7,6 @@ import Comment from "./Comment";
it("renders username and body", () => {
const props: PropTypesOf<typeof Comment> = {
id: "comment-id",
username: "Marvin",
body: "Woof",
createdAt: "1995-12-17T03:24:00.000Z",
@@ -13,7 +13,6 @@ import Username from "./Username";
import styles from "./Comment.css";
export interface CommentProps {
id?: string;
className?: string;
username: string | null;
body: string | null;
@@ -31,7 +30,7 @@ const Comment: StatelessComponent<CommentProps> = props => {
role="article"
className={cn(styles.root, { [styles.highlight]: props.highlight })}
>
<Flex direction="row" justifyContent="space-between" id={props.id}>
<Flex direction="row" justifyContent="space-between">
<TopBarLeft>
{props.username && <Username>{props.username}</Username>}
<Flex direction="row" alignItems="baseline" itemGutter>
@@ -7,7 +7,6 @@ exports[`renders username and body 1`] = `
>
<withPropsOnChange(Flex)
direction="row"
id="comment-id"
justifyContent="space-between"
>
<TopBarLeft>
@@ -1,15 +1,15 @@
import * as React from "react";
import { StatelessComponent } from "react";
import PermalinkViewQuery from "../queries/PermalinkViewQuery";
import StreamQuery from "../queries/StreamQuery";
import PermalinkView from "../views/permalink";
export interface CommentsPaneProps {
showPermalinkView: boolean;
}
const CommentsPane: StatelessComponent<CommentsPaneProps> = props => {
return props.showPermalinkView ? <PermalinkViewQuery /> : <StreamQuery />;
return props.showPermalinkView ? <PermalinkView /> : <StreamQuery />;
};
export default CommentsPane;
@@ -56,7 +56,6 @@ const EditCommentForm: StatelessComponent<EditCommentFormProps> = props => {
className={props.className}
autoComplete="off"
onSubmit={handleSubmit}
id={`comments-editCommentForm-form-${props.id}`}
>
<HorizontalGutter>
<div>
@@ -120,7 +119,6 @@ const EditCommentForm: StatelessComponent<EditCommentFormProps> = props => {
{props.expired ? (
<Localized id="comments-editCommentForm-close">
<Button
id={`comments-editCommentForm-closeButton-${props.id}`}
variant="outlined"
disabled={submitting}
onClick={props.onClose}
@@ -134,9 +132,6 @@ const EditCommentForm: StatelessComponent<EditCommentFormProps> = props => {
<>
<Localized id="comments-editCommentForm-cancel">
<Button
id={`comments-editCommentForm-cancelButton-${
props.id
}`}
variant="outlined"
disabled={submitting}
onClick={props.onCancel}
@@ -34,6 +34,7 @@ const ReplyList: StatelessComponent<ReplyListProps> = props => {
return (
<HorizontalGutter
id={`talk-comments-replyList-log--${props.comment.id}`}
data-testid={`commentReplyList-${props.comment.id}`}
role="log"
>
{props.comments.map(comment => (
@@ -0,0 +1,6 @@
.variantGhost {
&.colorRegular {
color: var(--palette-error-main);
opacity: 1;
}
}
@@ -0,0 +1,24 @@
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
import ReportButton from "./ReportButton";
it("renders report button", () => {
const props: PropTypesOf<typeof ReportButton> = {
reported: false,
};
const renderer = createRenderer();
renderer.render(<ReportButton {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it("renders reported button", () => {
const props: PropTypesOf<typeof ReportButton> = {
reported: true,
};
const renderer = createRenderer();
renderer.render(<ReportButton {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -0,0 +1,43 @@
import { Localized } from "fluent-react/compat";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import { Button, ButtonIcon, MatchMedia } from "talk-ui/components";
import styles from "./ReportButton.css";
interface Props extends PropTypesOf<typeof Button> {
reported: boolean;
}
class ReportButton extends React.Component<Props> {
public render() {
const { reported, accessKey, active, ...rest } = this.props;
return (
<Button
{...rest}
active={active}
disabled={!active && reported}
classes={(reported && !active && styles) || {}}
variant="ghost"
size="small"
>
<MatchMedia gtWidth="xs">
<ButtonIcon>flag</ButtonIcon>
</MatchMedia>
{!reported && (
<Localized id="comments-reportButton-report">
<span>Report</span>
</Localized>
)}
{reported && (
<Localized id="comments-reportButton-reported">
<span>Reported</span>
</Localized>
)}
</Button>
);
}
}
export default ReportButton;
@@ -0,0 +1,65 @@
import React from "react";
import { oncePerFrame } from "talk-common/utils";
import { ClickOutside, Popover } from "talk-ui/components";
import ReportCommentView from "../../views/reportComment";
import ReportButton from "./ReportButton";
import { PropTypesOf } from "talk-ui/types";
interface Props {
comment: { id: string } & PropTypesOf<typeof ReportCommentView>["comment"];
reported: boolean;
}
class ReportButtonWithPopover extends React.Component<Props> {
// Helper that prevents calling toggleVisibility more then once per frame.
// In essence this means we'll process an event only once.
// This might happen, when clicking on the button which will
// cause its onClick to happen as well as onClickOutside.
private toggleVisibilityOncePerFrame = oncePerFrame(
(toggleVisibility: () => void) => toggleVisibility()
);
public render() {
const { comment, reported } = this.props;
const popoverID = `report-popover-${comment.id}`;
return (
<Popover
id={popoverID}
placement="top-end"
description="A dialog for reporting comments"
body={({ toggleVisibility, scheduleUpdate }) => (
<ClickOutside
onClickOutside={() =>
this.toggleVisibilityOncePerFrame(toggleVisibility)
}
>
<ReportCommentView
comment={comment}
onClose={() =>
this.toggleVisibilityOncePerFrame(toggleVisibility)
}
onResize={scheduleUpdate}
/>
</ClickOutside>
)}
>
{({ toggleVisibility, forwardRef, visible }) => (
<ReportButton
onClick={() =>
!reported && this.toggleVisibilityOncePerFrame(toggleVisibility)
}
aria-controls={popoverID}
forwardRef={forwardRef}
active={visible}
reported={reported}
/>
)}
</Popover>
);
}
}
export default ReportButtonWithPopover;
@@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders report button 1`] = `
<withPropsOnChange(Button)
classes={Object {}}
disabled={false}
size="small"
variant="ghost"
>
<MatchMediaWithContext
gtWidth="xs"
>
<withPropsOnChange(ButtonIcon)>
flag
</withPropsOnChange(ButtonIcon)>
</MatchMediaWithContext>
<Localized
id="comments-reportButton-report"
>
<span>
Report
</span>
</Localized>
</withPropsOnChange(Button)>
`;
exports[`renders reported button 1`] = `
<withPropsOnChange(Button)
classes={
Object {
"colorRegular": "ReportButton-colorRegular",
"variantGhost": "ReportButton-variantGhost",
}
}
disabled={true}
size="small"
variant="ghost"
>
<MatchMediaWithContext
gtWidth="xs"
>
<withPropsOnChange(ButtonIcon)>
flag
</withPropsOnChange(ButtonIcon)>
</MatchMediaWithContext>
<Localized
id="comments-reportButton-reported"
>
<span>
Reported
</span>
</Localized>
</withPropsOnChange(Button)>
`;
@@ -0,0 +1,2 @@
export { default as ReportButton } from "./ReportButton";
export { default as ReportButtonWithPopover } from "./ReportButtonWithPopover";
@@ -49,6 +49,7 @@ const Stream: StatelessComponent<StreamProps> = props => {
</HorizontalGutter>
<HorizontalGutter
id="talk-comments-stream-log"
data-testid="comments-stream-log"
role="log"
aria-live="polite"
>
@@ -2,6 +2,7 @@
exports[`renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
data-testid="commentReplyList-comment-id"
id="talk-comments-replyList-log--comment-id"
role="log"
>
@@ -71,6 +72,7 @@ exports[`renders correctly 1`] = `
exports[`when there is more disables load more button 1`] = `
<withPropsOnChange(HorizontalGutter)
data-testid="commentReplyList-comment-id"
id="talk-comments-replyList-log--comment-id"
role="log"
>
@@ -154,6 +156,7 @@ exports[`when there is more disables load more button 1`] = `
exports[`when there is more renders a load more button 1`] = `
<withPropsOnChange(HorizontalGutter)
data-testid="commentReplyList-comment-id"
id="talk-comments-replyList-log--comment-id"
role="log"
>
@@ -23,6 +23,7 @@ exports[`renders correctly 1`] = `
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
aria-live="polite"
data-testid="comments-stream-log"
id="talk-comments-stream-log"
role="log"
>
@@ -149,6 +150,7 @@ exports[`when there is more disables load more button 1`] = `
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
aria-live="polite"
data-testid="comments-stream-log"
id="talk-comments-stream-log"
role="log"
>
@@ -289,6 +291,7 @@ exports[`when there is more renders a load more button 1`] = `
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
aria-live="polite"
data-testid="comments-stream-log"
id="talk-comments-stream-log"
role="log"
>
@@ -431,6 +434,7 @@ exports[`when use is logged in renders correctly 1`] = `
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
aria-live="polite"
data-testid="comments-stream-log"
id="talk-comments-stream-log"
role="log"
>
@@ -17,7 +17,7 @@ import {
withShowAuthPopupMutation,
} from "talk-stream/mutations";
import { Button, HorizontalGutter } from "talk-ui/components";
import { Button, Flex, HorizontalGutter } from "talk-ui/components";
import ReactionButtonContainer from "./ReactionButtonContainer";
import Comment, {
@@ -28,6 +28,7 @@ import ReplyButton from "../components/Comment/ReplyButton";
import EditCommentFormContainer from "./EditCommentFormContainer";
import PermalinkButtonContainer from "./PermalinkButtonContainer";
import ReplyCommentFormContainer from "./ReplyCommentFormContainer";
import ReportButtonContainer from "./ReportButtonContainer";
interface InnerProps {
me: MeData | null;
@@ -148,87 +149,96 @@ export class CommentContainer extends Component<InnerProps, State> {
const { showReplyDialog, showEditDialog, editable } = this.state;
if (showEditDialog) {
return (
<EditCommentFormContainer
comment={comment}
onClose={this.closeEditDialog}
/>
<div data-testid={`comment-${comment.id}`}>
<EditCommentFormContainer
comment={comment}
onClose={this.closeEditDialog}
/>
</div>
);
}
return (
<HorizontalGutter>
<Comment
id={`comment-${comment.id}`}
indentLevel={indentLevel}
username={comment.author && comment.author.username}
body={comment.body}
createdAt={comment.createdAt}
blur={comment.pending || false}
showEditedMarker={comment.editing.edited}
highlight={highlight}
parentAuthorName={
comment.parent &&
comment.parent.author &&
comment.parent.author.username
}
topBarRight={
(editable && (
<Localized id="comments-commentContainer-editButton">
<Button
id={`comments-commentContainer-editButton-${comment.id}`}
color="primary"
variant="underlined"
onClick={this.openEditDialog}
>
Edit
</Button>
</Localized>
)) ||
undefined
}
footer={
<>
<ButtonsBar>
{!disableReplies && (
<ReplyButton
id={`comments-commentContainer-replyButton-${comment.id}`}
onClick={this.openReplyDialog}
active={showReplyDialog}
<div data-testid={`comment-${comment.id}`}>
<HorizontalGutter>
<Comment
indentLevel={indentLevel}
username={comment.author && comment.author.username}
body={comment.body}
createdAt={comment.createdAt}
blur={comment.pending || false}
showEditedMarker={comment.editing.edited}
highlight={highlight}
parentAuthorName={
comment.parent &&
comment.parent.author &&
comment.parent.author.username
}
topBarRight={
(editable && (
<Localized id="comments-commentContainer-editButton">
<Button
color="primary"
variant="underlined"
onClick={this.openEditDialog}
>
Edit
</Button>
</Localized>
)) ||
undefined
}
footer={
<>
<Flex justifyContent="space-between">
<ButtonsBar>
{!disableReplies && (
<ReplyButton
id={`comments-commentContainer-replyButton-${
comment.id
}`}
onClick={this.openReplyDialog}
active={showReplyDialog}
/>
)}
<PermalinkButtonContainer
story={story}
commentID={comment.id}
/>
<ReactionButtonContainer
comment={comment}
settings={settings}
me={me}
/>
</ButtonsBar>
<ButtonsBar>
<ReportButtonContainer comment={comment} me={me} />
</ButtonsBar>
</Flex>
{showConversationLink && (
<ShowConversationLink
id={`comments-commentContainer-showConversation-${
comment.id
}`}
onClick={this.handleShowConversation}
href={getURLWithCommentID(
this.props.story.url,
this.props.comment.id
)}
/>
)}
<PermalinkButtonContainer
story={story}
commentID={comment.id}
/>
<ReactionButtonContainer
comment={comment}
settings={settings}
me={me}
/>
</ButtonsBar>
{showConversationLink && (
<ShowConversationLink
id={`comments-commentContainer-showConversation-${
comment.id
}`}
onClick={this.handleShowConversation}
href={getURLWithCommentID(
this.props.story.url,
this.props.comment.id
)}
/>
)}
</>
}
/>
{showReplyDialog && (
<ReplyCommentFormContainer
comment={comment}
story={story}
onClose={this.closeReplyDialog}
localReply={localReply}
</>
}
/>
)}
</HorizontalGutter>
{showReplyDialog && (
<ReplyCommentFormContainer
comment={comment}
story={story}
onClose={this.closeReplyDialog}
localReply={localReply}
/>
)}
</HorizontalGutter>
</div>
);
}
}
@@ -240,6 +250,7 @@ const enhanced = withSetCommentIDMutation(
fragment CommentContainer_me on User {
id
...ReactionButtonContainer_me
...ReportButtonContainer_me
}
`,
story: graphql`
@@ -271,6 +282,7 @@ const enhanced = withSetCommentIDMutation(
...ReplyCommentFormContainer_comment
...EditCommentFormContainer_comment
...ReactionButtonContainer_comment
...ReportButtonContainer_comment
}
`,
settings: graphql`
@@ -0,0 +1,67 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "talk-framework/lib/relay";
import { ReportButtonContainer_comment as CommentData } from "talk-stream/__generated__/ReportButtonContainer_comment.graphql";
import { ReportButtonContainer_me as MeData } from "talk-stream/__generated__/ReportButtonContainer_me.graphql";
import {
ReportButton,
ReportButtonWithPopover,
} from "../components/ReportButton";
import {
ShowAuthPopupMutation,
withShowAuthPopupMutation,
} from "talk-stream/mutations";
interface ReportButtonContainerProps {
comment: CommentData;
me: MeData | null;
showAuthPopup: ShowAuthPopupMutation;
}
class ReportButtonContainer extends React.Component<
ReportButtonContainerProps
> {
private get loggedIn() {
return this.props.me;
}
private handleSignIn = () => this.props.showAuthPopup({ view: "SIGN_IN" });
public render() {
const reported =
this.props.comment.myActionPresence &&
(this.props.comment.myActionPresence.flag ||
this.props.comment.myActionPresence.dontAgree);
return this.loggedIn ? (
<ReportButtonWithPopover
comment={this.props.comment}
reported={!!reported}
/>
) : (
<ReportButton onClick={this.handleSignIn} reported={!!reported} />
);
}
}
export default withShowAuthPopupMutation(
withFragmentContainer<ReportButtonContainerProps>({
me: graphql`
fragment ReportButtonContainer_me on User {
id
}
`,
comment: graphql`
fragment ReportButtonContainer_comment on Comment {
...ReportCommentFormContainer_comment
id
myActionPresence {
dontAgree
flag
}
}
`,
})(ReportButtonContainer)
);
@@ -1,316 +1,465 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`hide reply button 1`] = `
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<ButtonsBar>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up_alt",
"label": "Respect",
},
}
}
/>
</ButtonsBar>
</React.Fragment>
}
id="comment-comment-id"
indentLevel={1}
parentAuthorName={null}
showEditedMarker={false}
username="Marvin"
/>
</withPropsOnChange(HorizontalGutter)>
<div
data-testid="comment-comment-id"
>
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<withPropsOnChange(Flex)
justifyContent="space-between"
>
<ButtonsBar>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up_alt",
"label": "Respect",
},
}
}
/>
</ButtonsBar>
<ButtonsBar>
<withContext(createMutationContainer(Relay(ReportButtonContainer)))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
/>
</ButtonsBar>
</withPropsOnChange(Flex)>
</React.Fragment>
}
indentLevel={1}
parentAuthorName={null}
showEditedMarker={false}
username="Marvin"
/>
</withPropsOnChange(HorizontalGutter)>
</div>
`;
exports[`renders body only 1`] = `
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": null,
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up_alt",
"label": "Respect",
},
}
}
/>
</ButtonsBar>
</React.Fragment>
}
id="comment-comment-id"
indentLevel={1}
parentAuthorName={null}
showEditedMarker={false}
username={null}
/>
</withPropsOnChange(HorizontalGutter)>
<div
data-testid="comment-comment-id"
>
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<withPropsOnChange(Flex)
justifyContent="space-between"
>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": null,
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up_alt",
"label": "Respect",
},
}
}
/>
</ButtonsBar>
<ButtonsBar>
<withContext(createMutationContainer(Relay(ReportButtonContainer)))
comment={
Object {
"author": Object {
"id": "author-id",
"username": null,
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
/>
</ButtonsBar>
</withPropsOnChange(Flex)>
</React.Fragment>
}
indentLevel={1}
parentAuthorName={null}
showEditedMarker={false}
username={null}
/>
</withPropsOnChange(HorizontalGutter)>
</div>
`;
exports[`renders in reply to 1`] = `
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": Object {
"author": Object {
"username": "ParentAuthor",
},
},
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up_alt",
"label": "Respect",
},
}
}
/>
</ButtonsBar>
</React.Fragment>
}
id="comment-comment-id"
indentLevel={1}
parentAuthorName="ParentAuthor"
showEditedMarker={false}
username="Marvin"
/>
</withPropsOnChange(HorizontalGutter)>
<div
data-testid="comment-comment-id"
>
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<withPropsOnChange(Flex)
justifyContent="space-between"
>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": Object {
"author": Object {
"username": "ParentAuthor",
},
},
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up_alt",
"label": "Respect",
},
}
}
/>
</ButtonsBar>
<ButtonsBar>
<withContext(createMutationContainer(Relay(ReportButtonContainer)))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": Object {
"author": Object {
"username": "ParentAuthor",
},
},
"pending": false,
}
}
me={null}
/>
</ButtonsBar>
</withPropsOnChange(Flex)>
</React.Fragment>
}
indentLevel={1}
parentAuthorName="ParentAuthor"
showEditedMarker={false}
username="Marvin"
/>
</withPropsOnChange(HorizontalGutter)>
</div>
`;
exports[`renders username and body 1`] = `
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up_alt",
"label": "Respect",
},
}
}
/>
</ButtonsBar>
</React.Fragment>
}
id="comment-comment-id"
indentLevel={1}
parentAuthorName={null}
showEditedMarker={false}
username="Marvin"
/>
</withPropsOnChange(HorizontalGutter)>
<div
data-testid="comment-comment-id"
>
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<withPropsOnChange(Flex)
justifyContent="space-between"
>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up_alt",
"label": "Respect",
},
}
}
/>
</ButtonsBar>
<ButtonsBar>
<withContext(createMutationContainer(Relay(ReportButtonContainer)))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
/>
</ButtonsBar>
</withPropsOnChange(Flex)>
</React.Fragment>
}
indentLevel={1}
parentAuthorName={null}
showEditedMarker={false}
username="Marvin"
/>
</withPropsOnChange(HorizontalGutter)>
</div>
`;
exports[`shows conversation link 1`] = `
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
<div
data-testid="comment-comment-id"
>
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<withPropsOnChange(Flex)
justifyContent="space-between"
>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up",
"label": "Respect",
"labelActive": "Respected",
},
}
}
/>
</ButtonsBar>
<ButtonsBar>
<withContext(createMutationContainer(Relay(ReportButtonContainer)))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
/>
</ButtonsBar>
</withPropsOnChange(Flex)>
<ShowConversationLink
href="http://localhost/story?commentID=comment-id"
id="comments-commentContainer-showConversation-comment-id"
onClick={[Function]}
/>
<Relay(PermalinkButtonContainerProps)
commentID="comment-id"
story={
Object {
"url": "http://localhost/story",
}
}
/>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
comment={
Object {
"author": Object {
"id": "author-id",
"username": "Marvin",
},
"body": "Woof",
"createdAt": "1995-12-17T03:24:00.000Z",
"editing": Object {
"editableUntil": "1995-12-17T03:24:30.000Z",
"edited": false,
},
"id": "comment-id",
"parent": null,
"pending": false,
}
}
me={null}
settings={
Object {
"reaction": Object {
"icon": "thumb_up",
"label": "Respect",
"labelActive": "Respected",
},
}
}
/>
</ButtonsBar>
<ShowConversationLink
href="http://localhost/story?commentID=comment-id"
id="comments-commentContainer-showConversation-comment-id"
onClick={[Function]}
/>
</React.Fragment>
}
id="comment-comment-id"
indentLevel={1}
parentAuthorName={null}
showEditedMarker={false}
username="Marvin"
/>
</withPropsOnChange(HorizontalGutter)>
</React.Fragment>
}
indentLevel={1}
parentAuthorName={null}
showEditedMarker={false}
username="Marvin"
/>
</withPropsOnChange(HorizontalGutter)>
</div>
`;
@@ -5,12 +5,12 @@ import React, { StatelessComponent } from "react";
import { PropTypesOf } from "talk-framework/types";
import { Button, Counter, Flex, HorizontalGutter } from "talk-ui/components";
import CommentContainer from "../containers/CommentContainer";
import LocalReplyListContainer from "../containers/LocalReplyListContainer";
import { RootParent } from "./Comment";
import { Circle, Line } from "./Timeline";
import { RootParent } from "talk-stream/tabs/comments/components/Comment";
import CommentContainer from "talk-stream/tabs/comments/containers/CommentContainer";
import LocalReplyListContainer from "talk-stream/tabs/comments/containers/LocalReplyListContainer";
import styles from "./ConversationThread.css";
import { Circle, Line } from "./Timeline";
export interface ConversationThreadProps {
className?: string;
@@ -38,9 +38,13 @@ export interface ConversationThreadProps {
const ConversationThread: StatelessComponent<
ConversationThreadProps
> = props => {
const dataTestID = "comments-permalinkView-conversationThread";
if (props.remaining === 0 && props.parents.length === 0) {
return (
<div className={cn(props.className, styles.root)}>
<div
className={cn(props.className, styles.root)}
data-testid={dataTestID}
>
<CommentContainer
comment={props.comment}
story={props.story}
@@ -52,7 +56,7 @@ const ConversationThread: StatelessComponent<
);
}
return (
<div className={cn(props.className, styles.root)}>
<div className={cn(props.className, styles.root)} data-testid={dataTestID}>
<HorizontalGutter container={<Line dotted />}>
{props.rootParent && (
<Circle>
@@ -4,9 +4,10 @@ import React, { MouseEvent, StatelessComponent } from "react";
import { PropTypesOf } from "talk-framework/types";
import { Button, Flex, HorizontalGutter, Typography } from "talk-ui/components";
import UserBoxContainer from "../../../containers/UserBoxContainer";
import UserBoxContainer from "talk-stream/containers/UserBoxContainer";
import ReplyListContainer from "talk-stream/tabs/comments/containers/ReplyListContainer";
import ConversationThreadContainer from "../containers/ConversationThreadContainer";
import ReplyListContainer from "../containers/ReplyListContainer";
import styles from "./PermalinkView.css";
@@ -50,7 +51,6 @@ const PermalinkView: StatelessComponent<PermalinkViewProps> = ({
{showAllCommentsHref && (
<Localized id="comments-permalinkView-viewFullDiscussion">
<Button
id="talk-comments-permalinkView-viewFullDiscussion"
variant="underlined"
color="primary"
onClick={onShowAllComments}
@@ -3,6 +3,7 @@
exports[`renders with no parent comments 1`] = `
<div
className="root ConversationThread-root"
data-testid="comments-permalinkView-conversationThread"
>
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
comment={Object {}}
@@ -17,6 +18,7 @@ exports[`renders with no parent comments 1`] = `
exports[`with 2 remaining parent comments renders correctly 1`] = `
<div
className="root ConversationThread-root"
data-testid="comments-permalinkView-conversationThread"
>
<withPropsOnChange(HorizontalGutter)
container={
@@ -80,6 +82,7 @@ exports[`with 2 remaining parent comments renders correctly 1`] = `
exports[`with 2 remaining parent comments renders with disabled load more 1`] = `
<div
className="root ConversationThread-root"
data-testid="comments-permalinkView-conversationThread"
>
<withPropsOnChange(HorizontalGutter)
container={
@@ -39,7 +39,6 @@ exports[`renders comment not found 1`] = `
anchor={true}
color="primary"
href="http://localhost/link"
id="talk-comments-permalinkView-viewFullDiscussion"
onClick={[Function]}
target="_parent"
variant="underlined"
@@ -97,7 +96,6 @@ exports[`renders correctly 1`] = `
anchor={true}
color="primary"
href="http://localhost/link"
id="talk-comments-permalinkView-viewFullDiscussion"
onClick={[Function]}
target="_parent"
variant="underlined"
@@ -0,0 +1 @@
export { default } from "./queries/PermalinkViewQuery";
@@ -0,0 +1,18 @@
import { Component } from "react";
interface Props {
onMount: () => void;
}
/**
* A component that calls onMount, after componentDidMount.
*/
export default class PropagateUpdate extends Component<Props> {
public componentDidMount() {
this.props.onMount();
}
public render() {
return null;
}
}
@@ -0,0 +1,30 @@
.root {
width: 260px;
}
.title {
padding-right: calc(2 * var(--spacing-unit));
}
.list {
list-style: none;
padding: 0;
margin: 0;
}
.detail {
display: block;
color: var(--palette-text-secondary);
}
.textarea {
width: 100%;
height: 60px;
box-sizing: border-box;
border: 1px solid var(--palette-grey-light);
}
.textareaInfo {
margin-top: -2px;
text-align: right;
}
@@ -0,0 +1,220 @@
import { Localized } from "fluent-react/compat";
import { get } from "lodash";
import React, { StatelessComponent } from "react";
import { Field, Form } from "react-final-form";
import { OnSubmit } from "talk-framework/lib/form";
import { validateMaxLength } from "talk-framework/lib/validation";
import { PropTypesOf } from "talk-framework/types";
import {
Button,
Flex,
HorizontalGutter,
RadioButton,
Typography,
} from "talk-ui/components";
import PropagateMount from "./PropagateMount";
import styles from "./ReportCommentForm.css";
const RadioField: StatelessComponent<
Pick<
PropTypesOf<typeof Field>,
"validate" | "name" | "value" | "disabled" | "children"
>
> = ({ name, value, disabled, children }) => (
<Field name={name} type="radio" value={value}>
{({ input }) => (
<RadioButton
id={`reportComment-popover--${input.name}-${value}`}
name={input.name}
onChange={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
checked={input.checked}
disabled={disabled}
value={input.value}
>
{children}
</RadioButton>
)}
</Field>
);
interface InnerProps {
id: string;
onCancel: () => void;
onResize: () => void;
onSubmit: OnSubmit<FormProps>;
}
export interface FormProps {
reason:
| "COMMENT_REPORTED_OFFENSIVE"
| "COMMENT_REPORTED_SPAM"
| "COMMENT_REPORTED_OTHER"
| "DISAGREE";
additionalDetails?: string;
}
class ReportCommentForm extends React.Component<InnerProps> {
public render() {
const { onCancel, onSubmit, onResize, id } = this.props;
return (
<Form onSubmit={onSubmit}>
{({ handleSubmit, submitting, hasValidationErrors, form }) => (
<form
autoComplete="off"
onSubmit={handleSubmit}
className={styles.root}
id="report-comments-form"
>
<HorizontalGutter className={styles.root}>
<Localized id="comments-reportPopover-reportThisComment">
<Typography variant="heading2" className={styles.title}>
Report This Comment
</Typography>
</Localized>
<HorizontalGutter size="half">
<Localized id="comments-reportPopover-whyAreYouReporting">
<Typography variant="detail" className={styles.detail}>
Why are you reporting this comment?
</Typography>
</Localized>
<ul className={styles.list}>
<li>
<Localized id="comments-reportPopover-reasonOffensive">
<RadioField
name="reason"
value="COMMENT_REPORTED_OFFENSIVE"
disabled={submitting}
>
This comment is offensive
</RadioField>
</Localized>
</li>
<li>
<Localized id="comments-reportPopover-reasonIDisagree">
<RadioField
name="reason"
value="DISAGREE"
disabled={submitting}
>
I disagree with this comment
</RadioField>
</Localized>
</li>
<li>
<Localized id="comments-reportPopover-reasonSpam">
<RadioField
name="reason"
value="COMMENT_REPORTED_SPAM"
disabled={submitting}
>
This looks like an ad or marketing
</RadioField>
</Localized>
</li>
<li>
<Localized id="comments-reportPopover-reasonOther">
<RadioField
name="reason"
value="COMMENT_REPORTED_OTHER"
disabled={submitting}
>
Other
</RadioField>
</Localized>
</li>
</ul>
{get(form.getFieldState("reason"), "value") && (
<>
<PropagateMount onMount={onResize} />
<Localized id="comments-reportPopover-pleaseLeaveAdditionalInformation">
<Typography
variant="detail"
className={styles.detail}
container={
<label
htmlFor={`comments-reportCommentForm-aditionalDetails--${id}`}
/>
}
>
Please leave any additional information that may be
helpful to our moderators. (Optional)
</Typography>
</Localized>
<div>
<Field
name="additionalDetails"
validate={validateMaxLength(500)}
>
{({ input, meta }) => (
<div>
<textarea
id={`comments-reportCommentForm-aditionalDetails--${id}`}
className={styles.textarea}
onChange={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
disabled={submitting}
value={input.value}
/>
{(meta.error && (
<Typography
variant="detail"
color="error"
className={styles.textareaInfo}
>
{meta.error}
</Typography>
)) || (
<Localized
id="comments-reportPopover-maxCharacters"
$maxCharacters={500}
>
<Typography
variant="detail"
color="textSecondary"
className={styles.textareaInfo}
>
Max. 500 Characters
</Typography>
</Localized>
)}
</div>
)}
</Field>
</div>
</>
)}
</HorizontalGutter>
{get(form.getFieldState("reason"), "value") && (
<Flex alignItems="center" justifyContent="flex-end">
<Localized id="comments-reportPopover-cancel">
<Button color="primary" size="small" onClick={onCancel}>
Cancel
</Button>
</Localized>
<Localized id="comments-reportPopover-submit">
<Button
color="primary"
size="small"
variant="filled"
type="submit"
disabled={submitting || hasValidationErrors}
>
Submit
</Button>
</Localized>
</Flex>
)}
</HorizontalGutter>
</form>
)}
</Form>
);
}
}
export default ReportCommentForm;
@@ -0,0 +1,9 @@
.root {
padding: calc(1 * var(--spacing-unit));
}
.close {
position: absolute;
top: calc(0.5 * var(--spacing-unit));
right: calc(0.5 * var(--spacing-unit));
}
@@ -0,0 +1,21 @@
import { noop } from "lodash";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { removeFragmentRefs } from "talk-framework/testHelpers";
import { PropTypesOf } from "talk-framework/types";
import ReportPopover from "./ReportPopover";
const ReportPopoverN = removeFragmentRefs(ReportPopover);
it("renders correctly", () => {
const props: PropTypesOf<typeof ReportPopoverN> = {
comment: {},
onClose: noop,
onResize: noop,
};
const renderer = createRenderer();
renderer.render(<ReportPopoverN {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -0,0 +1,37 @@
import React from "react";
import { BaseButton, Icon } from "talk-ui/components";
import { PropTypesOf } from "talk-ui/types";
import ReportCommentFormContainer from "../containers/ReportCommentFormContainer";
import styles from "./ReportPopover.css";
interface InnerProps {
comment: PropTypesOf<typeof ReportCommentFormContainer>["comment"];
onClose: () => void;
onResize: () => void;
}
class ReportPopover extends React.Component<InnerProps> {
public render() {
const { onClose, onResize, comment } = this.props;
return (
<div className={styles.root}>
<BaseButton
onClick={onClose}
className={styles.close}
aria-label="Close Popover"
>
<Icon>close</Icon>
</BaseButton>
<ReportCommentFormContainer
comment={comment}
onClose={onClose}
onResize={onResize}
/>
</div>
);
}
}
export default ReportPopover;
@@ -0,0 +1,11 @@
.root {
width: 260px;
}
.title {
padding-right: calc(2 * var(--spacing-unit));
}
.dismiss {
margin-top: -3px;
}
@@ -0,0 +1,16 @@
import { noop } from "lodash";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
import ThankYou from "./ThankYou";
it("renders correctly", () => {
const props: PropTypesOf<typeof ThankYou> = {
onDismiss: noop,
};
const renderer = createRenderer();
renderer.render(<ThankYou {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -0,0 +1,52 @@
import { Localized } from "fluent-react/compat";
import React, { StatelessComponent } from "react";
import { Button, HorizontalGutter, Typography } from "talk-ui/components";
import styles from "./ThankYou.css";
interface InnerProps {
onDismiss: () => void;
}
class ReportForm extends React.Component<InnerProps> {
public render() {
const { onDismiss } = this.props;
const Content: StatelessComponent = ({ children }) => (
<Typography variant="bodyCopy">
{children}
{" "}
<Localized id="comments-reportPopover-dismiss">
<Button
className={styles.dismiss}
color="primary"
variant="underlined"
onClick={onDismiss}
>
Dismiss
</Button>
</Localized>
</Typography>
);
return (
<HorizontalGutter className={styles.root}>
<Localized id="comments-reportPopover-thankYou">
<Typography variant="heading2" className={styles.title}>
Thank you!
</Typography>
</Localized>
<div>
<Localized id="comments-reportPopover-receivedMessage">
<Content>
Weve received your message. Reports from members like you keep
the community safe.
</Content>
</Localized>
</div>
</HorizontalGutter>
);
}
}
export default ReportForm;
@@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<div
className="ReportPopover-root"
>
<withPropsOnChange(WithMouseHover)
aria-label="Close Popover"
className="ReportPopover-close"
onClick={[Function]}
>
<withPropsOnChange(Icon)>
close
</withPropsOnChange(Icon)>
</withPropsOnChange(WithMouseHover)>
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReportCommentFormContainer)))))
comment={Object {}}
onClose={[Function]}
onResize={[Function]}
/>
</div>
`;
@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
className="ThankYou-root"
>
<Localized
id="comments-reportPopover-thankYou"
>
<withPropsOnChange(Typography)
className="ThankYou-title"
variant="heading2"
>
Thank you!
</withPropsOnChange(Typography)>
</Localized>
<div>
<Localized
id="comments-reportPopover-receivedMessage"
>
<Content>
Weve received your message. Reports from members like you keep the community safe.
</Content>
</Localized>
</div>
</withPropsOnChange(HorizontalGutter)>
`;
@@ -0,0 +1,101 @@
import React, { Component } from "react";
import { graphql } from "react-relay";
import { BadUserInputError } from "talk-framework/lib/errors";
import { withFragmentContainer } from "talk-framework/lib/relay";
import { PropTypesOf } from "talk-framework/types";
import { ReportCommentFormContainer_comment as CommentData } from "talk-stream/__generated__/ReportCommentFormContainer_comment.graphql";
import {
CreateCommentDontAgreeMutation,
CreateCommentFlagMutation,
withCreateCommentDontAgreeMutation,
withCreateCommentFlagMutation,
} from "talk-stream/mutations";
import ReportCommentForm from "../components/ReportCommentForm";
import ThankYou from "../components/ThankYou";
interface Props {
comment: CommentData;
createCommentFlag: CreateCommentFlagMutation;
createCommentDontAgree: CreateCommentDontAgreeMutation;
onClose: () => void;
onResize: () => void;
}
interface State {
done: boolean;
}
export class ReportCommentFormContainer extends Component<Props, State> {
public state = {
done: false,
};
private handleOnSubmit: PropTypesOf<
typeof ReportCommentForm
>["onSubmit"] = async (input, form) => {
try {
if (input.reason === "DISAGREE") {
await this.props.createCommentDontAgree({
commentID: this.props.comment.id,
commentRevisionID: this.props.comment.revision.id,
additionalDetails: input.additionalDetails,
});
} else {
await this.props.createCommentFlag({
commentID: this.props.comment.id,
commentRevisionID: this.props.comment.revision.id,
reason: input.reason,
additionalDetails: input.additionalDetails,
});
}
this.setState({ done: true });
} catch (error) {
if (error instanceof BadUserInputError) {
return error.invalidArgsLocalized;
}
// tslint:disable-next-line:no-console
console.error(error);
}
return undefined;
};
public componentDidUpdate(prevProps: Props, prevState: State) {
// Reposition popper after switching view.
if (this.state.done && !prevState.done) {
this.props.onResize();
}
}
public render() {
if (!this.state.done) {
return (
<ReportCommentForm
id={this.props.comment.id}
onSubmit={this.handleOnSubmit}
onCancel={this.props.onClose}
onResize={this.props.onResize}
/>
);
}
return <ThankYou onDismiss={this.props.onClose} />;
}
}
const enhanced = withCreateCommentDontAgreeMutation(
withCreateCommentFlagMutation(
withFragmentContainer<Props>({
comment: graphql`
fragment ReportCommentFormContainer_comment on Comment {
id
revision {
id
}
}
`,
})(ReportCommentFormContainer)
)
);
export type ReportCommentFormContainerProps = PropTypesOf<typeof enhanced>;
export default enhanced;
@@ -0,0 +1 @@
export { default } from "./components/ReportPopover";
@@ -18,7 +18,7 @@ interface CommentHistoryProps {
const CommentHistory: StatelessComponent<CommentHistoryProps> = props => {
return (
<HorizontalGutter size="double">
<HorizontalGutter size="double" data-testid="profile-commentHistory">
<Localized id="profile-historyComment-commentHistory">
<Typography variant="heading3">Comment History</Typography>
</Localized>
@@ -11,6 +11,7 @@ const HistoryCommentN = removeFragmentRefs(HistoryComment);
it("renders correctly", () => {
const props: PropTypesOf<typeof HistoryCommentN> = {
id: "comment-id",
body: "Hello World",
createdAt: "2018-07-06T18:24:00.000Z",
replyCount: 4,
@@ -16,6 +16,7 @@ import HTMLContent from "../../../components/HTMLContent";
import styles from "./HistoryComment.css";
export interface HistoryCommentProps {
id: string;
body: string | null;
createdAt: string;
replyCount: number | null;
@@ -30,7 +31,7 @@ export interface HistoryCommentProps {
const HistoryComment: StatelessComponent<HistoryCommentProps> = props => {
return (
<HorizontalGutter>
<HorizontalGutter data-testid={`historyComment-${props.id}`}>
<Localized
id="profile-historyComment-story"
$title={props.story.metadata ? props.story.metadata.title : "N/A"} // FIXME: (wyattjoh) When a title for a Story isn't available, we need a fallback.
@@ -2,6 +2,7 @@
exports[`has more disables load more 1`] = `
<withPropsOnChange(HorizontalGutter)
data-testid="profile-commentHistory"
size="double"
>
<Localized
@@ -48,6 +49,7 @@ exports[`has more disables load more 1`] = `
exports[`has more renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
data-testid="profile-commentHistory"
size="double"
>
<Localized
@@ -94,6 +96,7 @@ exports[`has more renders correctly 1`] = `
exports[`renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
data-testid="profile-commentHistory"
size="double"
>
<Localized
@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
data-testid="historyComment-comment-id"
>
<Localized
$title="Story Title"
id="profile-historyComment-story"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,414 +1,77 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders permalink view with unknown comment 1`] = `
<div
className="HorizontalGutter-root App-root HorizontalGutter-full"
<section
aria-labelledby="tab-COMMENTS"
className="App-tabContent"
data-testid="current-tab-pane"
id="tabPane-COMMENTS"
role="tabpanel"
>
<ul
className="TabBar-root TabBar-primary"
role="tablist"
<div
className="HorizontalGutter-root PermalinkView-root HorizontalGutter-double"
>
<li
className="Tab-root"
id="tab-COMMENTS"
role="presentation"
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
>
<button
aria-controls="tabPane-COMMENTS"
aria-selected={true}
className="BaseButton-root Tab-button Tab-primary Tab-active"
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
role="tab"
type="button"
>
<span>
2 Comments
</span>
Sign in
</button>
</li>
</ul>
<section
aria-labelledby="tab-COMMENTS"
className="App-tabContent"
id="tabPane-COMMENTS"
role="tabpanel"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Register
</button>
</div>
<div
className="HorizontalGutter-root PermalinkView-root HorizontalGutter-double"
className="Flex-root Flex-flex Flex-justifyCenter Flex-alignCenter Flex-directionColumn"
>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Sign in
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Register
</button>
</div>
<div
className="Flex-root Flex-flex Flex-justifyCenter Flex-alignCenter Flex-directionColumn"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary PermalinkView-title1"
>
You are currently viewing a
</p>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary PermalinkView-title2"
>
SINGLE CONVERSATION
</p>
<a
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
href="http://localhost/"
id="talk-comments-permalinkView-viewFullDiscussion"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
target="_parent"
type="button"
>
View Full Discussion
</a>
</div>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary PermalinkView-title1"
>
Comment not found
You are currently viewing a
</p>
</div>
</section>
</div>
`;
exports[`show all comments 1`] = `
<div
className="HorizontalGutter-root App-root HorizontalGutter-full"
>
<ul
className="TabBar-root TabBar-primary"
role="tablist"
>
<li
className="Tab-root"
id="tab-COMMENTS"
role="presentation"
>
<button
aria-controls="tabPane-COMMENTS"
aria-selected={true}
className="BaseButton-root Tab-button Tab-primary Tab-active"
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary PermalinkView-title2"
>
SINGLE CONVERSATION
</p>
<a
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
href="http://localhost/"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
role="tab"
target="_parent"
type="button"
>
<span>
2 Comments
</span>
</button>
</li>
</ul>
<section
aria-labelledby="tab-COMMENTS"
className="App-tabContent"
id="tabPane-COMMENTS"
role="tabpanel"
>
<div
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
>
<div
className="HorizontalGutter-root HorizontalGutter-half"
>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Sign in
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Register
</button>
</div>
<div
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
>
<div
aria-hidden="true"
>
<div>
<div
className=""
>
<div
aria-hidden="true"
className="RTE-placeholder RTE-placeholder "
>
Post a comment
</div>
<div
aria-placeholder="Post a comment"
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
contentEditable={false}
dangerouslySetInnerHTML={
Object {
"__html": "",
}
}
disabled={true}
onBlur={[Function]}
onChange={[Function]}
onCut={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onPaste={[Function]}
onSelect={[Function]}
/>
<div
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Bold"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-md"
>
format_bold
</span>
</button>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Italic"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-md"
>
format_italic
</span>
</button>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Blockquote"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-md"
>
format_quote
</span>
</button>
</div>
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Sign in and Join the Conversation
</button>
</div>
</div>
<div
aria-live="polite"
className="HorizontalGutter-root HorizontalGutter-full"
id="talk-comments-stream-log"
role="log"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Indent-root"
>
<div
className=""
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<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>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
<div
className="Popover-root"
>
<button
aria-controls="permalink-popover-comment-0"
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Share
</span>
</button>
<div
aria-hidden={true}
aria-labelledby="permalink-popover-comment-0-ariainfo"
id="permalink-popover-comment-0"
role="popup"
>
<div
className="AriaInfo-root"
id="permalink-popover-comment-0-ariainfo"
>
A dialog showing a permalink to the comment
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Respect
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
View Full Discussion
</a>
</div>
</section>
</div>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Comment not found
</p>
</div>
</section>
`;
@@ -1,46 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders permalink view with unknown story 1`] = `
<div
className="HorizontalGutter-root App-root HorizontalGutter-full"
<section
aria-labelledby="tab-COMMENTS"
className="App-tabContent"
data-testid="current-tab-pane"
id="tabPane-COMMENTS"
role="tabpanel"
>
<ul
className="TabBar-root TabBar-primary"
role="tablist"
>
<li
className="Tab-root"
id="tab-COMMENTS"
role="presentation"
>
<button
aria-controls="tabPane-COMMENTS"
aria-selected={true}
className="BaseButton-root Tab-button Tab-primary Tab-active"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
role="tab"
type="button"
>
<span>
Comments
</span>
</button>
</li>
</ul>
<section
aria-labelledby="tab-COMMENTS"
className="App-tabContent"
id="tabPane-COMMENTS"
role="tabpanel"
>
<div>
Story not found
</div>
</section>
</div>
<div>
Story not found
</div>
</section>
`;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`create and remove reaction: Respected 1`] = `
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Respected
</span>
<span>
1
</span>
</button>
`;
exports[`create and remove reaction: Unrespected 1`] = `
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Respect
</span>
</button>
`;
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders comment stream 1`] = `
exports[`renders app with comment stream 1`] = `
<div
className="HorizontalGutter-root App-root HorizontalGutter-full"
>
@@ -35,6 +35,7 @@ exports[`renders comment stream 1`] = `
<section
aria-labelledby="tab-COMMENTS"
className="App-tabContent"
data-testid="current-tab-pane"
id="tabPane-COMMENTS"
role="tabpanel"
>
@@ -173,6 +174,7 @@ exports[`renders comment stream 1`] = `
<div
aria-live="polite"
className="HorizontalGutter-root HorizontalGutter-full"
data-testid="comments-stream-log"
id="talk-comments-stream-log"
role="log"
>
@@ -180,118 +182,144 @@ exports[`renders comment stream 1`] = `
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
data-testid="comment-comment-0"
>
<div
className="Indent-root"
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className=""
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-0"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-directionRow"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
Markus
</span>
</button>
<div
className="Popover-root"
>
<button
aria-controls="permalink-popover-comment-0"
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Share
</span>
</button>
<div
aria-hidden={true}
aria-labelledby="permalink-popover-comment-0-ariainfo"
id="permalink-popover-comment-0"
role="popup"
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<div
className="AriaInfo-root"
id="permalink-popover-comment-0-ariainfo"
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
A dialog showing a permalink to the comment
</div>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Flex-root Flex-flex Flex-justifySpaceBetween"
>
<span>
Respect
</span>
</button>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-0"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
<div
className="Popover-root"
>
<button
aria-controls="permalink-popover-comment-0"
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Share
</span>
</button>
<div
aria-hidden={true}
aria-labelledby="permalink-popover-comment-0-ariainfo"
id="permalink-popover-comment-0"
role="popup"
>
<div
className="AriaInfo-root"
id="permalink-popover-comment-0-ariainfo"
>
A dialog showing a permalink to the comment
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Respect
</span>
</button>
</div>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Report
</span>
</button>
</div>
</div>
</div>
</div>
</div>
@@ -303,118 +331,144 @@ exports[`renders comment stream 1`] = `
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
data-testid="comment-comment-1"
>
<div
className="Indent-root"
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className=""
className="Indent-root"
>
<div
className="Comment-root"
role="article"
className=""
>
<div
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-directionRow"
id="comment-comment-1"
className="Comment-root"
role="article"
>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-directionRow"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<div
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
/>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
Lukas
</span>
</button>
<div
className="Popover-root"
>
<button
aria-controls="permalink-popover-comment-1"
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Share
</span>
</button>
<div
aria-hidden={true}
aria-labelledby="permalink-popover-comment-1-ariainfo"
id="permalink-popover-comment-1"
role="popup"
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<div
className="AriaInfo-root"
id="permalink-popover-comment-1-ariainfo"
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
A dialog showing a permalink to the comment
</div>
2018-07-06T18:24:00.000Z
</time>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
/>
<div
className="Flex-root Flex-flex Flex-justifySpaceBetween"
>
<span>
Respect
</span>
</button>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton-comment-1"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Reply
</span>
</button>
<div
className="Popover-root"
>
<button
aria-controls="permalink-popover-comment-1"
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Share
</span>
</button>
<div
aria-hidden={true}
aria-labelledby="permalink-popover-comment-1-ariainfo"
id="permalink-popover-comment-1"
role="popup"
>
<div
className="AriaInfo-root"
id="permalink-popover-comment-1-ariainfo"
>
A dialog showing a permalink to the comment
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Respect
</span>
</button>
</div>
<div
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span>
Report
</span>
</button>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,501 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`close popup 1`] = `
<div
aria-hidden={true}
aria-labelledby="report-popover-comment-0-ariainfo"
id="report-popover-comment-0"
role="popup"
>
<div
className="AriaInfo-root"
id="report-popover-comment-0-ariainfo"
>
A dialog for reporting comments
</div>
</div>
`;
exports[`render popup 1`] = `
<div
aria-hidden={false}
aria-labelledby="report-popover-comment-0-ariainfo"
id="report-popover-comment-0"
role="popup"
>
<div
className="AriaInfo-root"
id="report-popover-comment-0-ariainfo"
>
A dialog for reporting comments
</div>
<div
className="Popover-popover Popover-top"
style={
Object {
"left": 0,
"opacity": 0,
"pointerEvents": "none",
"position": "absolute",
"top": 0,
}
}
>
<div
className="ReportPopover-root"
>
<button
aria-label="Close Popover"
className="BaseButton-root ReportPopover-close"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
close
</span>
</button>
<form
autoComplete="off"
className="ReportCommentForm-root"
id="report-comments-form"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root ReportCommentForm-root HorizontalGutter-full"
>
<h1
className="Typography-root Typography-heading2 Typography-colorTextPrimary ReportCommentForm-title"
>
Report This Comment
</h1>
<div
className="HorizontalGutter-root HorizontalGutter-half"
>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary ReportCommentForm-detail"
>
Why are you reporting this comment?
</p>
<ul
className="ReportCommentForm-list"
>
<li>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="reportComment-popover--reason-COMMENT_REPORTED_OFFENSIVE"
name="reason"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value="COMMENT_REPORTED_OFFENSIVE"
/>
<label
className="RadioButton-label"
htmlFor="reportComment-popover--reason-COMMENT_REPORTED_OFFENSIVE"
>
This comment is offensive
</label>
</div>
</li>
<li>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="reportComment-popover--reason-DISAGREE"
name="reason"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value="DISAGREE"
/>
<label
className="RadioButton-label"
htmlFor="reportComment-popover--reason-DISAGREE"
>
I disagree with this comment
</label>
</div>
</li>
<li>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="reportComment-popover--reason-COMMENT_REPORTED_SPAM"
name="reason"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value="COMMENT_REPORTED_SPAM"
/>
<label
className="RadioButton-label"
htmlFor="reportComment-popover--reason-COMMENT_REPORTED_SPAM"
>
This looks like an ad or marketing
</label>
</div>
</li>
<li>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="reportComment-popover--reason-COMMENT_REPORTED_OTHER"
name="reason"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value="COMMENT_REPORTED_OTHER"
/>
<label
className="RadioButton-label"
htmlFor="reportComment-popover--reason-COMMENT_REPORTED_OTHER"
>
Other
</label>
</div>
</li>
</ul>
</div>
</div>
</form>
</div>
</div>
</div>
`;
exports[`render popup expanded 1`] = `
<div
aria-hidden={false}
aria-labelledby="report-popover-comment-0-ariainfo"
id="report-popover-comment-0"
role="popup"
>
<div
className="AriaInfo-root"
id="report-popover-comment-0-ariainfo"
>
A dialog for reporting comments
</div>
<div
className="Popover-popover Popover-top"
style={
Object {
"left": 0,
"opacity": 0,
"pointerEvents": "none",
"position": "absolute",
"top": 0,
}
}
>
<div
className="ReportPopover-root"
>
<button
aria-label="Close Popover"
className="BaseButton-root ReportPopover-close"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
close
</span>
</button>
<form
autoComplete="off"
className="ReportCommentForm-root"
id="report-comments-form"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root ReportCommentForm-root HorizontalGutter-full"
>
<h1
className="Typography-root Typography-heading2 Typography-colorTextPrimary ReportCommentForm-title"
>
Report This Comment
</h1>
<div
className="HorizontalGutter-root HorizontalGutter-half"
>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary ReportCommentForm-detail"
>
Why are you reporting this comment?
</p>
<ul
className="ReportCommentForm-list"
>
<li>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={true}
className="RadioButton-input"
disabled={false}
id="reportComment-popover--reason-COMMENT_REPORTED_OFFENSIVE"
name="reason"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value="COMMENT_REPORTED_OFFENSIVE"
/>
<label
className="RadioButton-label"
htmlFor="reportComment-popover--reason-COMMENT_REPORTED_OFFENSIVE"
>
This comment is offensive
</label>
</div>
</li>
<li>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="reportComment-popover--reason-DISAGREE"
name="reason"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value="DISAGREE"
/>
<label
className="RadioButton-label"
htmlFor="reportComment-popover--reason-DISAGREE"
>
I disagree with this comment
</label>
</div>
</li>
<li>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="reportComment-popover--reason-COMMENT_REPORTED_SPAM"
name="reason"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value="COMMENT_REPORTED_SPAM"
/>
<label
className="RadioButton-label"
htmlFor="reportComment-popover--reason-COMMENT_REPORTED_SPAM"
>
This looks like an ad or marketing
</label>
</div>
</li>
<li>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="reportComment-popover--reason-COMMENT_REPORTED_OTHER"
name="reason"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value="COMMENT_REPORTED_OTHER"
/>
<label
className="RadioButton-label"
htmlFor="reportComment-popover--reason-COMMENT_REPORTED_OTHER"
>
Other
</label>
</div>
</li>
</ul>
<label
className="Typography-root Typography-detail Typography-colorTextPrimary ReportCommentForm-detail"
htmlFor="comments-reportCommentForm-aditionalDetails--comment-0"
>
Please leave any additional information that may be helpful to our moderators. (Optional)
</label>
<div>
<div>
<textarea
className="ReportCommentForm-textarea"
disabled={false}
id="comments-reportCommentForm-aditionalDetails--comment-0"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
value=""
/>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary ReportCommentForm-textareaInfo"
>
Max. 500 Characters
</p>
</div>
</div>
</div>
<div
className="Flex-root Flex-flex Flex-justifyFlexEnd Flex-alignCenter"
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantRegular"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Cancel
</button>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantFilled"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Submit
</button>
</div>
</div>
</form>
</div>
</div>
</div>
`;
exports[`report comment as offensive 1`] = `
<div
aria-hidden={false}
aria-labelledby="report-popover-comment-0-ariainfo"
id="report-popover-comment-0"
role="popup"
>
<div
className="AriaInfo-root"
id="report-popover-comment-0-ariainfo"
>
A dialog for reporting comments
</div>
<div
className="Popover-popover Popover-top"
style={
Object {
"left": 0,
"opacity": 0,
"pointerEvents": "none",
"position": "absolute",
"top": 0,
}
}
>
<div
className="ReportPopover-root"
>
<button
aria-label="Close Popover"
className="BaseButton-root ReportPopover-close"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
close
</span>
</button>
<div
className="HorizontalGutter-root ThankYou-root HorizontalGutter-full"
>
<h1
className="Typography-root Typography-heading2 Typography-colorTextPrimary ThankYou-title"
>
Thank you!
</h1>
<div>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Weve received your message. Reports from members like you keep the community safe.
 
<button
className="BaseButton-root Button-root ThankYou-dismiss Button-sizeRegular Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Dismiss
</button>
</p>
</div>
</div>
</div>
</div>
</div>
`;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,11 @@
import sinon from "sinon";
import timekeeper from "timekeeper";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
import {
createSinonStub,
waitForElement,
within,
} from "talk-framework/testHelpers";
import { settings, stories, users } from "../fixtures";
import create from "./create";
@@ -67,75 +70,90 @@ afterAll(() => {
});
it("edit a comment", async () => {
timekeeper.freeze(stories[0].comments.edges[0].node.createdAt);
const commentData = stories[0].comments.edges[0].node;
timekeeper.freeze(commentData.createdAt);
const testRenderer = createTestRenderer();
// Wait for loading.
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot("render stream");
const comment = await waitForElement(() =>
within(testRenderer.root).getByTestID(`comment-${commentData.id}`)
);
expect(within(comment).toJSON()).toMatchSnapshot(
"render comment with edit button"
);
// Open edit form.
testRenderer.root
.findByProps({ id: "comments-commentContainer-editButton-comment-0" })
within(comment)
.getByText("Edit")
.props.onClick();
expect(testRenderer.toJSON()).toMatchSnapshot("edit form");
expect(within(comment).toJSON()).toMatchSnapshot("edit form");
testRenderer.root
.findByProps({ inputId: "comments-editCommentForm-rte-comment-0" })
.findByProps({ inputId: `comments-editCommentForm-rte-${commentData.id}` })
.props.onChange({ html: "Edited!" });
testRenderer.root
.findByProps({ id: "comments-editCommentForm-form-comment-0" })
within(comment)
.getByType("form")
.props.onSubmit();
// Test optimistic response.
expect(testRenderer.toJSON()).toMatchSnapshot("optimistic response");
expect(within(comment).toJSON()).toMatchSnapshot("optimistic response");
// Wait for loading.
await timeout();
// Wait for server response.
await waitForElement(() =>
within(comment).getByText("Edited! (from server)")
);
// Test after server response.
expect(testRenderer.toJSON()).toMatchSnapshot("server response");
expect(within(comment).toJSON()).toMatchSnapshot("server response");
});
it("cancel edit", async () => {
timekeeper.freeze(stories[0].comments.edges[0].node.createdAt);
const commentData = stories[0].comments.edges[0].node;
timekeeper.freeze(commentData.createdAt);
const testRenderer = createTestRenderer();
await timeout();
const comment = await waitForElement(() =>
within(testRenderer.root).getByTestID(`comment-${commentData.id}`)
);
// Open edit form.
testRenderer.root
.findByProps({ id: "comments-commentContainer-editButton-comment-0" })
within(comment)
.getByText("Edit")
.props.onClick();
// Cancel edit form.
testRenderer.root
.findByProps({ id: "comments-editCommentForm-cancelButton-comment-0" })
within(comment)
.getByText("Cancel")
.props.onClick();
expect(testRenderer.toJSON()).toMatchSnapshot("edit canceled");
expect(within(comment).toJSON()).toMatchSnapshot();
});
it("shows expiry message", async () => {
timekeeper.freeze(stories[0].comments.edges[0].node.createdAt);
const commentData = stories[0].comments.edges[0].node;
timekeeper.freeze(commentData.createdAt);
const testRenderer = createTestRenderer();
await timeout();
const comment = await waitForElement(() =>
within(testRenderer.root).getByTestID(`comment-${commentData.id}`)
);
jest.useFakeTimers();
// Open edit form.
testRenderer.root
.findByProps({ id: "comments-commentContainer-editButton-comment-0" })
within(comment)
.getByText("Edit")
.props.onClick();
timekeeper.reset();
jest.runOnlyPendingTimers();
// Show edit time expired.
expect(testRenderer.toJSON()).toMatchSnapshot("edit time expired");
expect(within(comment).toJSON()).toMatchSnapshot("edit time expired");
// Close edit form.
testRenderer.root
.findByProps({ id: "comments-editCommentForm-closeButton-comment-0" })
within(comment)
.getByText("Close")
.props.onClick();
expect(testRenderer.toJSON()).toMatchSnapshot("edit form closed");
expect(within(comment).toJSON()).toMatchSnapshot("edit form closed");
});
@@ -1,8 +1,12 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
import {
createSinonStub,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import { comments, settings, stories } from "../fixtures";
import create from "./create";
@@ -80,18 +84,33 @@ beforeEach(() => {
}));
});
it("renders comment stream", async () => {
// Wait for loading.
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
it("renders comment stream with load more button", async () => {
const streamLog = await waitForElement(() =>
within(testRenderer.root).getByTestID("comments-stream-log")
);
expect(within(streamLog).toJSON()).toMatchSnapshot();
});
it("loads more comments", async () => {
testRenderer.root
.findByProps({ id: "talk-comments-stream-loadMore" })
const streamLog = await waitForElement(() =>
within(testRenderer.root).getByTestID("comments-stream-log")
);
// Get amount of comments before.
const commentsBefore = within(streamLog).getAllByTestID(/^comment-/).length;
within(streamLog)
.getByText("Load More")
.props.onClick();
// Wait for loading.
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
// Wait for load more button to disappear
// Should now have one more comment
await wait(() =>
expect(within(streamLog).queryByText("Load More")).toBeNull()
);
expect(within(streamLog).getAllByTestID(/^comment-/).length).toBe(
commentsBefore + 1
);
});
@@ -1,8 +1,11 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
import {
createSinonStub,
waitForElement,
within,
} from "talk-framework/testHelpers";
import { comments, commentWithReplies, settings, stories } from "../fixtures";
import create from "./create";
@@ -73,21 +76,27 @@ beforeEach(() => {
});
it("renders permalink view", async () => {
// Wait for loading.
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
const tabPane = await waitForElement(() =>
within(testRenderer.root).getByTestID("current-tab-pane")
);
expect(within(tabPane).toJSON()).toMatchSnapshot();
});
it("show all comments", async () => {
const mockEvent = {
preventDefault: sinon.mock().once(),
};
testRenderer.root
.findByProps({
id: "talk-comments-permalinkView-viewFullDiscussion",
})
const tabPane = await waitForElement(() =>
within(testRenderer.root).getByTestID("current-tab-pane")
);
within(tabPane)
.getByText("View Full Discussion")
.props.onClick(mockEvent);
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
await waitForElement(() =>
within(tabPane).getByTestID("comments-stream-log")
);
mockEvent.preventDefault.verify();
});
@@ -1,8 +1,11 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
import {
createSinonStub,
waitForElement,
within,
} from "talk-framework/testHelpers";
import { comments, settings, stories } from "../fixtures";
import create from "./create";
@@ -54,9 +57,10 @@ beforeEach(() => {
});
it("renders permalink view with unknown comment", async () => {
// Wait for loading.
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
const tabPane = await waitForElement(() =>
within(testRenderer.root).getByTestID("current-tab-pane")
);
expect(within(tabPane).toJSON()).toMatchSnapshot();
});
it("show all comments", async () => {
@@ -64,12 +68,16 @@ it("show all comments", async () => {
preventDefault: sinon.mock().once(),
};
testRenderer.root
.findByProps({
id: "talk-comments-permalinkView-viewFullDiscussion",
})
const tabPane = await waitForElement(() =>
within(testRenderer.root).getByTestID("current-tab-pane")
);
within(tabPane)
.getByText("View Full Discussion")
.props.onClick(mockEvent);
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
await waitForElement(() =>
within(tabPane).getByTestID("comments-stream-log")
);
mockEvent.preventDefault.verify();
});
@@ -1,8 +1,12 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
import {
createSinonStub,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import { comments, settings, stories } from "../fixtures";
import create from "./create";
@@ -86,20 +90,33 @@ beforeEach(() => {
}));
});
it("renders permalink view", async () => {
// Wait for loading.
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
it("renders conversation thread", async () => {
const conversationThread = await waitForElement(() =>
within(testRenderer.root).getByTestID(
"comments-permalinkView-conversationThread"
)
);
expect(within(conversationThread).toJSON()).toMatchSnapshot();
});
it("views pervious comments", async () => {
testRenderer.root
.find(
node =>
node.props.onClick &&
node.props.id === "comments-conversationThread-showHiddenComments"
it("shows hidden comments", async () => {
const conversationThread = await waitForElement(() =>
within(testRenderer.root).getByTestID(
"comments-permalinkView-conversationThread"
)
);
// Show hidden comments.
within(conversationThread)
.getByText("SHOW HIDDEN COMMENTS")
.props.onClick();
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
// Wait until button disappears.
await wait(() =>
expect(
within(conversationThread).queryByText("SHOW HIDDEN COMMENTS")
).toBeNull()
);
expect(within(conversationThread).toJSON()).toMatchSnapshot();
});
@@ -1,8 +1,7 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { waitForElement, within } from "talk-framework/testHelpers";
import create from "talk-stream/test/comments/create";
import { settings } from "talk-stream/test/fixtures";
@@ -28,7 +27,8 @@ beforeEach(() => {
});
it("renders permalink view with unknown story", async () => {
// Wait for loading.
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
const tabPane = await waitForElement(() =>
within(testRenderer.root).getByTestID("current-tab-pane")
);
expect(within(tabPane).toJSON()).toMatchSnapshot();
});

Some files were not shown because too many files have changed in this diff Show More