Implement reply

This commit is contained in:
Chi Vinh Le
2018-09-04 19:47:09 +02:00
parent ee30003390
commit 110becb075
26 changed files with 2076 additions and 84 deletions
@@ -12,7 +12,7 @@ export class FakeStorage implements PymStorage {
return Promise.resolve();
}
public getItem(key: string) {
return Promise.resolve(this.store[key]);
return Promise.resolve(this.store[key] || null);
}
}
@@ -1,12 +1,42 @@
import { ComponentType } from "react";
/** Remove all traces of `$fragmentRefs` and `$refType` from type recursively */
/** Remove `$fragmentRefs` and `$refType` on a single object */
export type OmitFragments<T> = Pick<
T,
{
[P in keyof T]: P extends " $fragmentRefs" | " $refType" ? never : P
}[keyof T]
>;
export type NoFragmentRefs<T> = T extends object
? {
[P in Exclude<keyof T, " $fragmentRefs" | " $refType">]: NoFragmentRefs<
T[P]
>
}
? T extends ((...args: any[]) => any)
? T
: T extends ReadonlyArray<infer U>
? ReadonlyArray<NoFragmentRefs2<U>> // TODO: (cvle) this should normally reference itself but it complains about a circular reference.
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
: T;
// TODO: (cvle) these NoFragmentRefX are a workaround for above issue
export type NoFragmentRefs2<T> = T extends object
? T extends ((...args: any[]) => any)
? T
: T extends ReadonlyArray<infer U>
? ReadonlyArray<NoFragmentRefs3<U>>
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
: T;
export type NoFragmentRefs3<T> = T extends object
? T extends ((...args: any[]) => any)
? T
: T extends ReadonlyArray<infer U>
? ReadonlyArray<NoFragmentRefs4<U>>
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
: T;
export type NoFragmentRefs4<T> = T extends object
? T extends ((...args: any[]) => any)
? T
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
: T;
export default function removeFragmentRefs<T>(
@@ -0,0 +1,20 @@
import { shallow } from "enzyme";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import IndentedComment from "./IndentedComment";
it("renders correctly", () => {
const props: PropTypesOf<typeof IndentedComment> = {
indentLevel: 1,
id: "comment-id",
author: {
username: "Marvin",
},
body: "Woof",
createdAt: "1995-12-17T03:24:00.000Z",
};
const wrapper = shallow(<IndentedComment {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -0,0 +1,17 @@
import { shallow } from "enzyme";
import { noop } from "lodash";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import ReplyButton from "./ReplyButton";
it("renders correctly", () => {
const props: PropTypesOf<typeof ReplyButton> = {
id: "id",
onClick: noop,
active: true,
};
const wrapper = shallow(<ReplyButton {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -4,12 +4,14 @@ import React, { EventHandler, MouseEvent, StatelessComponent } from "react";
import { Button, ButtonIcon, MatchMedia } from "talk-ui/components";
interface Props {
id?: string;
onClick?: EventHandler<MouseEvent<HTMLButtonElement>>;
active?: boolean;
}
const ReplyButton: StatelessComponent<Props> = props => (
<Button
id={props.id}
onClick={props.onClick}
variant="ghost"
size="small"
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<Indent
level={1}
>
<Comment
author={
Object {
"username": "Marvin",
}
}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
id="comment-id"
/>
</Indent>
`;
@@ -0,0 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<withPropsOnChange(Button)
active={true}
id="id"
onClick={[Function]}
size="small"
variant="ghost"
>
<MatchMediaWithContext
gtWidth="xs"
>
<withPropsOnChange(ButtonIcon)>
reply
</withPropsOnChange(ButtonIcon)>
</MatchMediaWithContext>
<Localized
id="comments-replyButton-reply"
>
<span>
Reply
</span>
</Localized>
</withPropsOnChange(Button)>
`;
@@ -20,6 +20,7 @@ interface FormProps {
}
export interface ReplyCommentFormProps {
id: string;
className?: string;
onSubmit: OnSubmit<FormProps>;
onCancel?: EventHandler<MouseEvent<any>>;
@@ -27,73 +28,73 @@ export interface ReplyCommentFormProps {
initialValues?: FormProps;
}
const ReplyCommentForm: StatelessComponent<ReplyCommentFormProps> = props => (
<Form onSubmit={props.onSubmit} initialValues={props.initialValues}>
{({ handleSubmit, submitting }) => (
<form
className={props.className}
autoComplete="off"
onSubmit={handleSubmit}
id="comments-replyCommentForm-form"
>
<FormSpy onChange={props.onChange} />
<HorizontalGutter>
<Field name="body" validate={required}>
{({ input, meta }) => (
<div>
<Localized id="comments-replyCommentForm-rteLabel">
<AriaInfo
component="label"
htmlFor="comments-replyCommentForm-field"
const ReplyCommentForm: StatelessComponent<ReplyCommentFormProps> = props => {
const inputID = `comments-replyCommentForm-rte-${props.id}`;
return (
<Form onSubmit={props.onSubmit} initialValues={props.initialValues}>
{({ handleSubmit, submitting }) => (
<form
className={props.className}
autoComplete="off"
onSubmit={handleSubmit}
id={`comments-replyCommentForm-form-${props.id}`}
>
<FormSpy onChange={props.onChange} />
<HorizontalGutter>
<Field name="body" validate={required}>
{({ input, meta }) => (
<div>
<Localized id="comments-replyCommentForm-rteLabel">
<AriaInfo component="label" htmlFor={inputID}>
Write a reply
</AriaInfo>
</Localized>
<Localized
id="comments-replyCommentForm-rte"
attrs={{ placeholder: true }}
>
Write a reply
</AriaInfo>
</Localized>
<Localized
id="comments-replyCommentForm-rte"
attrs={{ placeholder: true }}
<RTE
inputId={inputID}
onChange={({ html }) => input.onChange(html)}
value={input.value}
placeholder="Write a reply"
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<Typography align="right" color="error" gutterBottom>
{meta.error || meta.submitError}
</Typography>
)}
</div>
)}
</Field>
<Flex direction="row" justifyContent="flex-end" itemGutter="half">
<Localized id="comments-replyCommentForm-cancel">
<Button
variant="outlined"
disabled={submitting}
onClick={props.onCancel}
>
<RTE
inputId="comments-replyCommentForm-field"
onChange={({ html }) => input.onChange(html)}
value={input.value}
placeholder="Write a reply"
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<Typography align="right" color="error" gutterBottom>
{meta.error || meta.submitError}
</Typography>
)}
</div>
)}
</Field>
<Flex direction="row" justifyContent="flex-end" itemGutter="half">
<Localized id="comments-replyCommentForm-cancel">
<Button
variant="outlined"
disabled={submitting}
onClick={props.onCancel}
>
Cancel
</Button>
</Localized>
<Localized id="comments-replyCommentForm-submit">
<Button
color="primary"
variant="filled"
disabled={submitting}
type="submit"
>
Submit
</Button>
</Localized>
</Flex>
</HorizontalGutter>
</form>
)}
</Form>
);
Cancel
</Button>
</Localized>
<Localized id="comments-replyCommentForm-submit">
<Button
color="primary"
variant="filled"
disabled={submitting}
type="submit"
>
Submit
</Button>
</Localized>
</Flex>
</HorizontalGutter>
</form>
)}
</Form>
);
};
export default ReplyCommentForm;
@@ -49,6 +49,7 @@ export class CommentContainer extends Component<InnerProps, State> {
footer={
<>
<ReplyButton
id={`comments-commentContainer-replyButton-${comment.id}`}
onClick={this.openReplyDialog}
active={showReplyDialog}
/>
@@ -0,0 +1,171 @@
import { shallow } from "enzyme";
import { noop } from "lodash";
import React from "react";
import sinon from "sinon";
import { PropTypesOf } from "talk-framework/types";
import { timeout } from "talk-common/utils";
import {
createFakePymStorage,
removeFragmentRefs,
} from "talk-framework/testHelpers";
import { ReplyCommentFormContainer } from "./ReplyCommentFormContainer";
const ReplyCommentFormContainerN = removeFragmentRefs(
ReplyCommentFormContainer
);
function getContextKey(commentID: string) {
return `replyCommentFormBody-${commentID}`;
}
it("renders correctly", async () => {
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
// tslint:disable-next-line:no-empty
createComment: (() => {}) as any,
asset: {
id: "asset-id",
},
comment: {
id: "comment-id",
},
pymSessionStorage: createFakePymStorage(),
};
const wrapper = shallow(<ReplyCommentFormContainerN {...props} />);
await timeout();
wrapper.update();
expect(wrapper).toMatchSnapshot();
});
it("renders with initialValues", async () => {
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
// tslint:disable-next-line:no-empty
createComment: (() => {}) as any,
asset: {
id: "asset-id",
},
comment: {
id: "comment-id",
},
pymSessionStorage: createFakePymStorage(),
};
await props.pymSessionStorage.setItem(
getContextKey(props.comment.id),
"Hello World!"
);
const wrapper = shallow(<ReplyCommentFormContainerN {...props} />);
await timeout();
wrapper.update();
expect(wrapper).toMatchSnapshot();
});
it("save values", async () => {
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
// tslint:disable-next-line:no-empty
createComment: (() => {}) as any,
asset: {
id: "asset-id",
},
comment: {
id: "comment-id",
},
pymSessionStorage: createFakePymStorage(),
};
await props.pymSessionStorage.setItem(
getContextKey(props.comment.id),
"Hello World!"
);
const wrapper = shallow(<ReplyCommentFormContainerN {...props} />);
await timeout();
wrapper.update();
wrapper
.first()
.props()
.onChange({ values: { body: "changed" } });
expect(
await props.pymSessionStorage.getItem(getContextKey(props.comment.id))
).toBe("changed");
});
it("creates a comment", async () => {
const assetID = "asset-id";
const input = { body: "Hello World!" };
const createCommentStub = sinon.stub();
const form = { reset: noop };
const onCloseStub = sinon.stub();
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
// tslint:disable-next-line:no-empty
createComment: createCommentStub,
asset: {
id: "asset-id",
},
comment: {
id: "comment-id",
},
pymSessionStorage: createFakePymStorage(),
onClose: onCloseStub,
};
await props.pymSessionStorage.setItem(
getContextKey(props.comment.id),
"Hello World!"
);
const wrapper = shallow(<ReplyCommentFormContainerN {...props} />);
await timeout();
wrapper.update();
wrapper
.first()
.props()
.onSubmit(input, form);
expect(
createCommentStub.calledWith({
assetID,
parentID: props.comment.id,
...input,
})
).toBeTruthy();
await timeout();
expect(onCloseStub.calledOnce).toBe(true);
});
it("closes on cancel", async () => {
const onCloseStub = sinon.stub();
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
// tslint:disable-next-line:no-empty
createComment: (() => {}) as any,
asset: {
id: "asset-id",
},
comment: {
id: "comment-id",
},
pymSessionStorage: createFakePymStorage(),
onClose: onCloseStub,
};
await props.pymSessionStorage.setItem(
getContextKey(props.comment.id),
"Hello World!"
);
const wrapper = shallow(<ReplyCommentFormContainerN {...props} />);
await timeout();
wrapper.update();
wrapper.findWhere(w => !!w.prop("onCancel")).prop("onCancel")();
// Calls close.
expect(onCloseStub.calledOnce).toBe(true);
// Removes saved value.
expect(
await props.pymSessionStorage.getItem(getContextKey(props.comment.id))
).toBeNull();
});
@@ -67,6 +67,7 @@ export class ReplyCommentFormContainer extends Component<InnerProps, State> {
parentID: this.props.comment.id,
...input,
});
this.props.pymSessionStorage.removeItem(this.contextKey);
if (this.props.onClose) {
this.props.onClose();
@@ -95,6 +96,7 @@ export class ReplyCommentFormContainer extends Component<InnerProps, State> {
}
return (
<ReplyCommentForm
id={this.props.comment.id}
onSubmit={this.handleOnSubmit}
onChange={this.handleOnChange}
initialValues={this.state.initialValues}
@@ -18,6 +18,15 @@ interface InnerProps {
relay: RelayPaginationProp;
}
// tslint:disable-next-line:no-unused-expression
graphql`
fragment StreamContainer_comment on Comment {
id
...CommentContainer_comment
...ReplyListContainer_comment
}
`;
export class StreamContainer extends React.Component<InnerProps> {
public state = {
disableLoadMore: false,
@@ -81,9 +90,7 @@ const enhanced = withPaginationContainer<
@connection(key: "Stream_comments") {
edges {
node {
id
...CommentContainer_comment
...ReplyListContainer_comment
...StreamContainer_comment @relay(mask: false)
}
}
}
@@ -14,6 +14,7 @@ exports[`renders body only 1`] = `
<React.Fragment>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton"
onClick={[Function]}
/>
<withContext(withLocalStateContainer(PermalinkContainer))
@@ -41,6 +42,7 @@ exports[`renders username and body 1`] = `
<React.Fragment>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton"
onClick={[Function]}
/>
<withContext(withLocalStateContainer(PermalinkContainer))
@@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<ReplyCommentForm
onCancel={[Function]}
onChange={[Function]}
onSubmit={[Function]}
/>
`;
exports[`renders with initialValues 1`] = `
<ReplyCommentForm
initialValues={
Object {
"body": "Hello World!",
}
}
onCancel={[Function]}
onChange={[Function]}
onSubmit={[Function]}
/>
`;
@@ -22,13 +22,7 @@ const mutation = graphql`
edge {
cursor
node {
id
author {
id
username
}
body
createdAt
...StreamContainer_comment @relay(mask: false)
}
}
clientMutationId
@@ -98,7 +92,7 @@ function commit(environment: Environment, input: CreateCommentInput) {
},
clientMutationId: (clientMutationId++).toString(),
},
},
} as any, // TODO: (cvle) generated types should contain one for the optimistic response.
configs: getConfig(input),
});
}
@@ -181,6 +181,7 @@ exports[`loads more comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -233,6 +234,7 @@ exports[`loads more comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -285,6 +287,7 @@ exports[`loads more comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -487,6 +490,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -539,6 +543,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -56,6 +56,7 @@ exports[`renders permalink view 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -256,6 +257,7 @@ exports[`show all comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -213,6 +213,7 @@ exports[`show all comments 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -213,6 +213,7 @@ exports[`post a comment 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -265,6 +266,7 @@ exports[`post a comment 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -317,6 +319,7 @@ exports[`post a comment 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -557,6 +560,7 @@ exports[`post a comment 2`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -609,6 +613,7 @@ exports[`post a comment 2`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -661,6 +666,7 @@ exports[`post a comment 2`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -901,6 +907,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -953,6 +960,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
File diff suppressed because it is too large Load Diff
@@ -181,6 +181,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -233,6 +234,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -289,6 +291,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -341,6 +344,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -181,6 +181,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -233,6 +234,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -181,6 +181,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -237,6 +238,7 @@ exports[`renders comment stream 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -461,6 +463,7 @@ exports[`show all replies 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -517,6 +520,7 @@ exports[`show all replies 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -569,6 +573,7 @@ exports[`show all replies 1`] = `
>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
id="comments-commentContainer-replyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
+3
View File
@@ -19,18 +19,21 @@ export const comments = [
author: users[0],
body: "Joining Too",
createdAt: "2018-07-06T18:24:00.000Z",
replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } },
},
{
id: "comment-1",
author: users[1],
body: "What's up?",
createdAt: "2018-07-06T18:20:00.000Z",
replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } },
},
{
id: "comment-2",
author: users[2],
body: "Hey!",
createdAt: "2018-07-06T18:14:00.000Z",
replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } },
},
];
@@ -0,0 +1,102 @@
import { ReactTestRenderer } from "react-test-renderer";
import timekeeper from "timekeeper";
import { timeout } from "talk-common/utils";
import { createSinonStub } from "talk-framework/testHelpers";
import create from "./create";
import { assets, users } from "./fixtures";
let testRenderer: ReactTestRenderer;
beforeEach(() => {
const resolvers = {
Query: {
asset: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0])
),
me: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { clientAuthRevision: 0 }).returns(users[0])
),
},
Mutation: {
createComment: createSinonStub(
s => s.throws(),
s =>
s
.withArgs(undefined, {
input: {
assetID: assets[0].id,
parentID: assets[0].comments.edges[0].node.id,
body: "<strong>Hello world!</strong>",
clientMutationId: "0",
},
})
.returns({
commentEdge: {
cursor: "2018-07-06T18:24:00.000Z",
node: {
id: "comment-x",
author: users[0],
body: "<strong>Hello world! (from server)</strong>",
createdAt: "2018-07-06T18:24:00.000Z",
replies: {
edges: [],
pageInfo: { endCursor: null, hasNextPage: false },
},
},
},
clientMutationId: "0",
})
),
},
};
({ testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
localRecord.setValue(assets[0].id, "assetID");
},
}));
});
it("renders comment stream", async () => {
// Wait for loading.
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
});
it("post a reply", async () => {
// Wait for loading.
await timeout();
// Open reply form.
testRenderer.root
.findByProps({ id: "comments-commentContainer-replyButton-comment-0" })
.props.onClick();
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
// Write reply .
testRenderer.root
.findByProps({ inputId: "comments-replyCommentForm-rte-comment-0" })
.props.onChange({ html: "<strong>Hello world!</strong>" });
timekeeper.freeze(new Date("2018-07-06T18:24:00.000Z"));
testRenderer.root
.findByProps({ id: "comments-replyCommentForm-form-comment-0" })
.props.onSubmit();
// Test optimistic response.
expect(testRenderer.toJSON()).toMatchSnapshot();
timekeeper.reset();
// Wait for loading.
await timeout();
// Test after server response.
expect(testRenderer.toJSON()).toMatchSnapshot();
});
+6
View File
@@ -48,3 +48,9 @@ comments-postCommentFormFake-rte =
comments-replyButton-reply = Reply
comments-permalinkViewQuery-assetNotFound = { comments-streamQuery-assetNotFound }
comments-replyCommentForm-submit = Submit
comments-replyCommentForm-cancel = Cancel
comments-replyCommentForm-rteLabel = Write a reply
comments-replyCommentForm-rte =
.placeholder = { comments-postCommentForm-rteLabel }