mirror of
https://github.com/wassname/talk.git
synced 2026-07-04 13:58:12 +08:00
Implement reply
This commit is contained in:
@@ -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))
|
||||
|
||||
+22
@@ -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]}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user