mirror of
https://github.com/wassname/talk.git
synced 2026-08-13 12:40:11 +08:00
[CORL-729] Upgrade Final Form & improve tests (#2735)
* CORL-729 Upgrade final form, fix and improve tests This is a squashed rebase from these commits: a300b31c23ab11e5e6f0668bc03ece7697360aaa feat: error on optimisic response warnings during test dd8a9776865ec41d346e23ae0743d0d4fb0caa21 fix: turn off @typescript-eslint/prefer-regexp-exec rule b995daacf1722cace60d755e672cb6a3a20d6bc2 feat: mute false warnings in test e44f9e28307cd63a82c1fb7ac013667dd7b7bc46 fix: wrap remaining tests with act afbd4329b97f3dbc9f873ea4ff234d98bb651ccf feat: fail when act(async () => ...) without await warnings 51dfb60b7d75411ba2e1a28db33f4aba5cf84de1 feat: fail tests with act warnings 97f93546ed8113e207882411eb4cdb7675b0796c fix: mock window.resizeTo globally in tests 850958b8c4d2fc0aca67ae580296433af223f8ab fix: more tests with act 24c05ab88e9a416e4962acc3f20f2c764ba07657 fix: wrap charCountEditComment test in acts ed590b82d147470bba74055dc682e6b70d2e76fb fix signInWithEmail 4a2b9402cb6ce9565d99ae1a950eaa422ff603c3 fix: PostCommentFormContainer 815ebe6ef364d954d4bd0a35495934c9d014170b fix: use final form initialize instead of reset and remove obsolete d3101f2112ed3ffe8d06609620e31e6655d2cf6d Merge branch 'feature/CORL-729-final-form' of https://github.com/coralproject/talk into feature/CORL-729-final-form a0658da610a5f39b6fae78ffb8dd291b22d54e50 fix: addEmailAddress 60f7fc99a44dfa49dfd401a0ef49c60973b3e8e3 fix: use proper act pattern in renderConfigure.spec.tsx d66bdfc2245c2b1ee03a1b3a3a56f1d5ba14ddc4 Stop propagation of Modal content click events (#2706) ec6689594136e22a5b9f05ea284162702dc4955d fix: use proper act pattern in createUsername.spec.tsx ef239496964a5f9a91ee1c4424ad81537c4f47fe fix: stream configuration c7e06a0c6aef6b299c41392af81f8a20855028bf fix: user proper act pattern in streamConfiguration 9712e659e394a898500ed649464ff14d4870e589 Merge branch 'feature/CORL-729-final-form' of https://github.com/coralproject/talk into feature/CORL-729-final-form 9e5cfbaf3593615b457055de23f187fa07edd4c4 fix: signIn test 99b44a4a1bbb7ff2cd44c6821ad33b80f90c4a99 fix: user proper act pattern in stories.spec.tsx ed7c1a92f93ab9aaa85ff92837b0ed21560cb358 fix: user proper act pattern in addEmailAddress a04b392cb2148b9a24791b062027796c409d053e fix: remove obsolete snapshots 59df67c0f9b9d26c74e2cca7d333f5868b0b202d fix: signUp test 7656f179df95b4cd96b37afbc88a1c3a2944fdf4 fix: use proper act pattern on createPassword 85246fbf1f9ab49ad3a09c11ab79bf537059b548 fix: use correct act() pattern in createUsername.spec.tsx d5239373a2d1bbed0bfe8c1ca62ef6a70ef5c7d9 fix: the correct way to use act on form submit :-) d84ecd168354f4acb422a5ddb725fb8faf9c3184 fix: moderation test d8df62ab1a6486144684ff917c47e6e375ffbe03 fix: reportComment test 2756e3184bb292168e8d34e201f340c3799941e6 fix: auth tests a28695dbdd313a7bc3dade9ac1f92d6ef0061526 Properly handle final-form actions in tests 2fafc8ea3458c5b15b66f3d65f0947672dd1a516 Update snapshots now that final form isn't overwriting props 1f9bbaec8678a7653124898ba4a2e84ddc1ef243 fix: prevent final form from overwriting field props f6c66c003d1917db2dcb3f757e8a303266c381fa fix: prevent final form fields overwrite out props 48d1fc7318ee4ba7bf72839127e9a0b1487c1729 chore: rename translation string 728373da5728a4e7c039bd0c3a3cf0037e9f5177 fix: purge relay metadata from request 7cdea925087a6b9b6e318bbb1b31b798be87dc2f fix: radiobuttons 9735bae79222219a81a28d458976a596201b650b chore: revert obsolete checks 0b556e1693584430a5814e81d87b0f233efd1a30 fix: refactor admin configure b245afc7b196035bcb454e031c966e63c77ce522 fix: implement withForm HOC 5787400051211f5d2e1773d7207f32b66b02a2a1 Update the Configure page form state to properly load form values 8c2af3e22a96a3d2e50e7f06fb45d1fb79cf0c8e Replace form.reset() with setTimeout(form.reset) 27d9c90e3f0166cc2db45db461619be15a3cb950 Update radio buttons and on/off fields to work with final-form updates b852dd14af85b14ff8e0d2823e1e83bf278b29b9 Replace any on OnSubmit with typed form submission interfaces f049a70aaf4872825ac3b2aa62dc5cb7f945290a (f) Preliminarily get Coral compiling with latest final-form libraries * feat: act is now actAndReturn * fix: print original filename and line number in custom console impl * feat: trace process warnings * fix: server warnings about potentially memory leak with too many listeners
This commit is contained in:
@@ -37,7 +37,7 @@ export interface EditCommentFormProps {
|
||||
} | null;
|
||||
createdAt: string;
|
||||
editableUntil: string;
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
onSubmit: OnSubmit<any>;
|
||||
onCancel?: EventHandler<MouseEvent<any>>;
|
||||
onClose?: EventHandler<MouseEvent<any>>;
|
||||
initialValues?: FormProps;
|
||||
|
||||
+1
@@ -59,6 +59,7 @@ const ModerationActionsContainer: FunctionComponent<Props> = ({
|
||||
await reject({
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
storyID: story.id,
|
||||
});
|
||||
}, [approve, comment, story]);
|
||||
const onFeature = useCallback(() => {
|
||||
|
||||
+5
-2
@@ -5,9 +5,10 @@ import { CoralContext } from "coral-framework/lib/bootstrap";
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
createMutation,
|
||||
lookup,
|
||||
MutationInput,
|
||||
} from "coral-framework/lib/relay";
|
||||
import { GQLCOMMENT_STATUS } from "coral-framework/schema";
|
||||
import { GQLComment, GQLCOMMENT_STATUS } from "coral-framework/schema";
|
||||
import { RejectCommentEvent } from "coral-stream/events";
|
||||
|
||||
import { RejectCommentMutation as MutationTypes } from "coral-stream/__generated__/RejectCommentMutation.graphql";
|
||||
@@ -18,7 +19,7 @@ const RejectCommentMutation = createMutation(
|
||||
"rejectComment",
|
||||
async (
|
||||
environment: Environment,
|
||||
input: MutationInput<MutationTypes> & { noEmit?: boolean },
|
||||
input: MutationInput<MutationTypes> & { storyID: string; noEmit?: boolean },
|
||||
{ eventEmitter }: CoralContext
|
||||
) => {
|
||||
let rejectCommentEvent: ReturnType<
|
||||
@@ -65,7 +66,9 @@ const RejectCommentMutation = createMutation(
|
||||
comment: {
|
||||
id: input.commentID,
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
tags: lookup<GQLComment>(environment, input.commentID)!.tags,
|
||||
story: {
|
||||
id: input.storyID,
|
||||
commentCounts: {
|
||||
tags: {
|
||||
FEATURED: 0,
|
||||
|
||||
@@ -36,9 +36,9 @@ interface FormProps {
|
||||
export interface ReplyCommentFormProps {
|
||||
id: string;
|
||||
className?: string;
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
onSubmit: OnSubmit<any>;
|
||||
onCancel?: EventHandler<MouseEvent<any>>;
|
||||
onChange?: (state: FormState, form: FormApi) => void;
|
||||
onChange?: (state: FormState<any>, form: FormApi) => void;
|
||||
initialValues?: FormProps;
|
||||
rteRef?: Ref<CoralRTE>;
|
||||
parentUsername: string | null;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { get } from "lodash";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import { Field, FieldProps, Form } from "react-final-form";
|
||||
|
||||
import { OnSubmit } from "coral-framework/lib/form";
|
||||
import { validateMaxLength } from "coral-framework/lib/validation";
|
||||
import { PropTypesOf } from "coral-framework/types";
|
||||
import CLASSES from "coral-stream/classes";
|
||||
import ValidationMessage from "coral-stream/common/ValidationMessage";
|
||||
import {
|
||||
@@ -22,16 +21,17 @@ import styles from "./ReportCommentForm.css";
|
||||
|
||||
const RadioField: FunctionComponent<
|
||||
Pick<
|
||||
PropTypesOf<typeof Field>,
|
||||
FieldProps<string, any>,
|
||||
"validate" | "name" | "value" | "disabled" | "children"
|
||||
>
|
||||
> = ({ name, value, disabled, children }) => (
|
||||
<Field name={name} type="radio" value={value}>
|
||||
{({ input }) => (
|
||||
<RadioButton
|
||||
{...input}
|
||||
id={`reportComment-popover--${input.name}-${value}`}
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
value={value}
|
||||
>
|
||||
{children}
|
||||
</RadioButton>
|
||||
@@ -43,7 +43,7 @@ interface Props {
|
||||
id: string;
|
||||
onCancel: () => void;
|
||||
onResize: () => void;
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
onSubmit: OnSubmit<any>;
|
||||
}
|
||||
|
||||
export interface FormProps {
|
||||
@@ -156,10 +156,10 @@ class ReportCommentForm extends React.Component<Props> {
|
||||
{({ input, meta }) => (
|
||||
<div>
|
||||
<textarea
|
||||
{...input}
|
||||
id={`comments-reportCommentForm-aditionalDetails--${id}`}
|
||||
className={styles.textarea}
|
||||
disabled={submitting}
|
||||
{...input}
|
||||
/>
|
||||
{/* TODO: (wyattjoh) check to see if this should be replaced by the framework validation message */}
|
||||
{(meta.error && (
|
||||
|
||||
+1
@@ -49,6 +49,7 @@ const UserBanPopoverContainer: FunctionComponent<Props> = ({
|
||||
reject({
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
storyID: story.id,
|
||||
noEmit: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, { FunctionComponent, useCallback } from "react";
|
||||
import { Field, Form, FormSpy } from "react-final-form";
|
||||
|
||||
import { useViewerEvent } from "coral-framework/lib/events";
|
||||
import { OnSubmit } from "coral-framework/lib/form";
|
||||
import { FormError, OnSubmit } from "coral-framework/lib/form";
|
||||
import { PropTypesOf } from "coral-framework/types";
|
||||
import CLASSES from "coral-stream/classes";
|
||||
import ValidationMessage from "coral-stream/common/ValidationMessage";
|
||||
@@ -24,9 +24,11 @@ interface FormProps {
|
||||
body: string;
|
||||
}
|
||||
|
||||
interface FormSubmitProps extends FormProps, FormError {}
|
||||
|
||||
interface Props {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
onChange?: (state: FormState, form: FormApi) => void;
|
||||
onSubmit: OnSubmit<FormSubmitProps>;
|
||||
onChange?: (state: FormState<any>, form: FormApi) => void;
|
||||
initialValues?: FormProps;
|
||||
min: number | null;
|
||||
max: number | null;
|
||||
|
||||
+106
-42
@@ -5,7 +5,7 @@ import sinon from "sinon";
|
||||
|
||||
import { pureMerge, timeout } from "coral-common/utils";
|
||||
import { createPromisifiedStorage } from "coral-framework/lib/storage";
|
||||
import { removeFragmentRefs } from "coral-framework/testHelpers";
|
||||
import { act, removeFragmentRefs, wait } from "coral-framework/testHelpers";
|
||||
import { DeepPartial, PropTypesOf } from "coral-framework/types";
|
||||
|
||||
import { PostCommentFormContainer } from "./PostCommentFormContainer";
|
||||
@@ -59,20 +59,34 @@ function createDefaultProps(add: DeepPartial<Props> = {}): Props {
|
||||
it("renders correctly", async () => {
|
||||
const props = createDefaultProps();
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
act(() => {
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await wait(() => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("renders with initialValues", async () => {
|
||||
const props = createDefaultProps();
|
||||
|
||||
await props.sessionStorage.setItem(contextKey, "Hello World!");
|
||||
|
||||
await act(async () => {
|
||||
await props.sessionStorage.setItem(contextKey, "Hello World!");
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
act(() => {
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await wait(() => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("save values", async () => {
|
||||
@@ -82,22 +96,32 @@ it("save values", async () => {
|
||||
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
wrapper
|
||||
.first()
|
||||
.props()
|
||||
.onChange({ values: { body: "changed" } });
|
||||
expect(await props.sessionStorage.getItem(contextKey)).toBe("changed");
|
||||
|
||||
act(() => {
|
||||
wrapper.update();
|
||||
});
|
||||
act(() => {
|
||||
wrapper
|
||||
.first()
|
||||
.props()
|
||||
.onChange({ values: { body: "changed" } });
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await wait(async () =>
|
||||
expect(await props.sessionStorage.getItem(contextKey)).toBe("changed")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a comment", async () => {
|
||||
const storyID = "story-id";
|
||||
const input = { body: "Hello World!" };
|
||||
const createCommentStub = sinon.stub().returns({ edge: { node: {} } });
|
||||
const form = { reset: noop };
|
||||
const form = { initialize: noop };
|
||||
const formMock = sinon.mock(form);
|
||||
formMock
|
||||
.expects("reset")
|
||||
.expects("initialize")
|
||||
.withArgs({})
|
||||
.once();
|
||||
|
||||
@@ -109,24 +133,37 @@ it("creates a comment", async () => {
|
||||
},
|
||||
});
|
||||
|
||||
await props.sessionStorage.setItem(contextKey, "Hello World!");
|
||||
await act(async () => {
|
||||
await props.sessionStorage.setItem(contextKey, "Hello World!");
|
||||
});
|
||||
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
wrapper
|
||||
.first()
|
||||
.props()
|
||||
.onSubmit(input, form);
|
||||
expect(
|
||||
createCommentStub.calledWith({
|
||||
storyID,
|
||||
nudge: true,
|
||||
...input,
|
||||
})
|
||||
).toBeTruthy();
|
||||
await timeout();
|
||||
formMock.verify();
|
||||
|
||||
act(() => {
|
||||
wrapper.update();
|
||||
});
|
||||
act(() => {
|
||||
wrapper
|
||||
.first()
|
||||
.props()
|
||||
.onSubmit(input, form);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await wait(() =>
|
||||
expect(
|
||||
createCommentStub.calledWith({
|
||||
storyID,
|
||||
nudge: true,
|
||||
...input,
|
||||
})
|
||||
).toBeTruthy()
|
||||
);
|
||||
});
|
||||
await act(async () => {
|
||||
await wait(() => formMock.verify());
|
||||
});
|
||||
});
|
||||
|
||||
it("renders when story has been closed (collapsing)", async () => {
|
||||
@@ -142,8 +179,14 @@ it("renders when story has been closed (collapsing)", async () => {
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
act(() => {
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await wait(() => expect(wrapper).toMatchSnapshot());
|
||||
});
|
||||
});
|
||||
|
||||
it("renders when commenting has been disabled (collapsing)", async () => {
|
||||
@@ -157,8 +200,14 @@ it("renders when commenting has been disabled (collapsing)", async () => {
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
act(() => {
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await wait(() => expect(wrapper).toMatchSnapshot());
|
||||
});
|
||||
});
|
||||
|
||||
it("renders when story has been closed (non-collapsing)", async () => {
|
||||
@@ -184,8 +233,14 @@ it("renders when story has been closed (non-collapsing)", async () => {
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.setProps(nextProps);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
act(() => {
|
||||
wrapper.setProps(nextProps);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await wait(() => expect(wrapper).toMatchSnapshot());
|
||||
});
|
||||
});
|
||||
|
||||
it("renders when commenting has been disabled (non-collapsing)", async () => {
|
||||
@@ -207,8 +262,14 @@ it("renders when commenting has been disabled (non-collapsing)", async () => {
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.setProps(nextProps);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
act(() => {
|
||||
wrapper.setProps(nextProps);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await wait(() => expect(wrapper).toMatchSnapshot());
|
||||
});
|
||||
});
|
||||
|
||||
it("renders when user is scheduled to be deleted", async () => {
|
||||
@@ -219,5 +280,8 @@ it("renders when user is scheduled to be deleted", async () => {
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
await act(async () => {
|
||||
await wait(() => expect(wrapper).toMatchSnapshot());
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
})
|
||||
);
|
||||
if (submitStatus !== "RETRY") {
|
||||
form.reset({});
|
||||
form.initialize({});
|
||||
}
|
||||
this.setState({ submitStatus, nudge: true });
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
import { FormInitializer } from "coral-framework/lib/form";
|
||||
import { purgeMetadata } from "coral-framework/lib/relay";
|
||||
import { PropTypesOf } from "coral-framework/types";
|
||||
import CLASSES from "coral-stream/classes";
|
||||
import {
|
||||
@@ -23,83 +23,59 @@ import styles from "./ConfigureStream.css";
|
||||
|
||||
interface Props {
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
storySettings: PropTypesOf<typeof PremodConfigContainer>["storySettings"] &
|
||||
PropTypesOf<typeof PremodLinksConfigContainer>["storySettings"] &
|
||||
PropTypesOf<typeof MessageBoxConfigContainer>["storySettings"] &
|
||||
PropTypesOf<typeof LiveUpdatesConfigContainer>["storySettings"] &
|
||||
PropTypesOf<typeof LiveUpdatesConfigContainer>["storySettingsReadOnly"];
|
||||
storySettings: PropTypesOf<
|
||||
typeof LiveUpdatesConfigContainer
|
||||
>["storySettings"];
|
||||
}
|
||||
|
||||
const ConfigureStream: FunctionComponent<Props> = ({
|
||||
onSubmit,
|
||||
storySettings,
|
||||
}) => (
|
||||
<Form onSubmit={onSubmit}>
|
||||
{({ handleSubmit, submitting, pristine, form, submitError }) => (
|
||||
<FormInitializer form={form}>
|
||||
{({ onInitValues }) => (
|
||||
<form
|
||||
className={CLASSES.configureCommentStream.$root}
|
||||
autoComplete="off"
|
||||
onSubmit={handleSubmit}
|
||||
id="configure-form"
|
||||
>
|
||||
<Flex
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-start"
|
||||
itemGutter
|
||||
<Form onSubmit={onSubmit} initialValues={purgeMetadata(storySettings)}>
|
||||
{({ handleSubmit, submitting, pristine, submitError }) => (
|
||||
<form
|
||||
className={CLASSES.configureCommentStream.$root}
|
||||
autoComplete="off"
|
||||
onSubmit={handleSubmit}
|
||||
id="configure-form"
|
||||
>
|
||||
<Flex justifyContent="space-between" alignItems="flex-start" itemGutter>
|
||||
<Localized id="configure-stream-title">
|
||||
<Typography variant="heading2" className={styles.heading}>
|
||||
Configure this Comment Stream
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Localized id="configure-stream-apply">
|
||||
<Button
|
||||
className={CLASSES.configureCommentStream.applyButton}
|
||||
color="success"
|
||||
variant="filled"
|
||||
type="submit"
|
||||
disabled={submitting || pristine}
|
||||
>
|
||||
<Localized id="configure-stream-title">
|
||||
<Typography variant="heading2" className={styles.heading}>
|
||||
Configure this Comment Stream
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Localized id="configure-stream-apply">
|
||||
<Button
|
||||
className={CLASSES.configureCommentStream.applyButton}
|
||||
color="success"
|
||||
variant="filled"
|
||||
type="submit"
|
||||
disabled={submitting || pristine}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</Localized>
|
||||
</Flex>
|
||||
<HorizontalGutter size="double">
|
||||
{submitError && (
|
||||
<CallOut
|
||||
className={CLASSES.configureCommentStream.errorMessage}
|
||||
color="error"
|
||||
>
|
||||
{submitError}
|
||||
</CallOut>
|
||||
)}
|
||||
<LiveUpdatesConfigContainer
|
||||
onInitValues={onInitValues}
|
||||
storySettings={storySettings}
|
||||
storySettingsReadOnly={storySettings}
|
||||
disabled={submitting}
|
||||
/>
|
||||
<PremodConfigContainer
|
||||
onInitValues={onInitValues}
|
||||
storySettings={storySettings}
|
||||
disabled={submitting}
|
||||
/>
|
||||
<PremodLinksConfigContainer
|
||||
onInitValues={onInitValues}
|
||||
storySettings={storySettings}
|
||||
disabled={submitting}
|
||||
/>
|
||||
<MessageBoxConfigContainer
|
||||
onInitValues={onInitValues}
|
||||
storySettings={storySettings}
|
||||
disabled={submitting}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
</FormInitializer>
|
||||
Apply
|
||||
</Button>
|
||||
</Localized>
|
||||
</Flex>
|
||||
<HorizontalGutter size="double">
|
||||
{submitError && (
|
||||
<CallOut
|
||||
className={CLASSES.configureCommentStream.errorMessage}
|
||||
color="error"
|
||||
>
|
||||
{submitError}
|
||||
</CallOut>
|
||||
)}
|
||||
<LiveUpdatesConfigContainer
|
||||
storySettings={storySettings}
|
||||
disabled={submitting}
|
||||
/>
|
||||
<PremodConfigContainer disabled={submitting} />
|
||||
<PremodLinksConfigContainer disabled={submitting} />
|
||||
<MessageBoxConfigContainer disabled={submitting} />
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -49,11 +49,12 @@ const enhanced = withFragmentContainer<Props>({
|
||||
fragment ConfigureStreamContainer_story on Story {
|
||||
id
|
||||
settings {
|
||||
...PremodConfigContainer_storySettings
|
||||
...PremodLinksConfigContainer_storySettings
|
||||
...MessageBoxConfigContainer_storySettings
|
||||
...PremodConfig_formValues @relay(mask: false)
|
||||
...PremodLinksConfig_formValues @relay(mask: false)
|
||||
...MessageBoxConfig_formValues @relay(mask: false)
|
||||
...LiveUpdatesConfig_formValues @relay(mask: false)
|
||||
|
||||
...LiveUpdatesConfigContainer_storySettings
|
||||
...LiveUpdatesConfigContainer_storySettingsReadOnly
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
+11
-1
@@ -1,12 +1,22 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { parseBool } from "coral-framework/lib/form";
|
||||
|
||||
import ToggleConfig from "../ToggleConfig";
|
||||
import WidthLimitedDescription from "../WidthLimitedDescription";
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
graphql`
|
||||
fragment LiveUpdatesConfig_formValues on StorySettings {
|
||||
live {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
@@ -15,9 +25,9 @@ const LiveUpdatesConfig: FunctionComponent<Props> = ({ disabled }) => (
|
||||
<Field name="live.enabled" type="checkbox" parse={parseBool}>
|
||||
{({ input }) => (
|
||||
<ToggleConfig
|
||||
{...input}
|
||||
id={input.name}
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
title={
|
||||
<Localized id="configure-liveUpdates-title">
|
||||
<span>Enable Live Updates for this Story</span>
|
||||
|
||||
+1
-16
@@ -4,27 +4,19 @@ import { graphql } from "react-relay";
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
|
||||
import { LiveUpdatesConfigContainer_storySettings } from "coral-stream/__generated__/LiveUpdatesConfigContainer_storySettings.graphql";
|
||||
import { LiveUpdatesConfigContainer_storySettingsReadOnly } from "coral-stream/__generated__/LiveUpdatesConfigContainer_storySettingsReadOnly.graphql";
|
||||
|
||||
import LiveUpdatesConfig from "./LiveUpdatesConfig";
|
||||
|
||||
interface Props {
|
||||
storySettings: LiveUpdatesConfigContainer_storySettings;
|
||||
storySettingsReadOnly: LiveUpdatesConfigContainer_storySettingsReadOnly;
|
||||
onInitValues: (values: LiveUpdatesConfigContainer_storySettings) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class LiveUpdatesConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.storySettings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
disabled,
|
||||
storySettingsReadOnly: {
|
||||
storySettings: {
|
||||
live: { configurable },
|
||||
},
|
||||
} = this.props;
|
||||
@@ -40,13 +32,6 @@ class LiveUpdatesConfigContainer extends React.Component<Props> {
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
storySettings: graphql`
|
||||
fragment LiveUpdatesConfigContainer_storySettings on StorySettings {
|
||||
live {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
`,
|
||||
storySettingsReadOnly: graphql`
|
||||
fragment LiveUpdatesConfigContainer_storySettingsReadOnly on StorySettings {
|
||||
live {
|
||||
configurable
|
||||
}
|
||||
|
||||
+15
-3
@@ -1,6 +1,7 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent, Suspense } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { MarkdownEditor } from "coral-framework/components/loadables";
|
||||
import {
|
||||
@@ -25,11 +26,22 @@ import {
|
||||
Typography,
|
||||
} from "coral-ui/components";
|
||||
|
||||
import ToggleConfig from "../ToggleConfig";
|
||||
import WidthLimitedDescription from "../WidthLimitedDescription";
|
||||
import ToggleConfig from "./ToggleConfig";
|
||||
import WidthLimitedDescription from "./WidthLimitedDescription";
|
||||
|
||||
import styles from "./MessageBoxConfig.css";
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
graphql`
|
||||
fragment MessageBoxConfig_formValues on StorySettings {
|
||||
messageBox {
|
||||
enabled
|
||||
content
|
||||
icon
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
@@ -38,10 +50,10 @@ const MessageBoxConfig: FunctionComponent<Props> = ({ disabled }) => (
|
||||
<Field name="messageBox.enabled" type="checkbox" parse={parseBool}>
|
||||
{({ input }) => (
|
||||
<ToggleConfig
|
||||
{...input}
|
||||
className={CLASSES.configureMessageBox.$root}
|
||||
id={input.name}
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
title={
|
||||
<Localized id="configure-messageBox-title">
|
||||
<span>Enable Message Box for this Stream</span>
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
|
||||
import { MessageBoxConfigContainer_storySettings as StorySettingsData } from "coral-stream/__generated__/MessageBoxConfigContainer_storySettings.graphql";
|
||||
|
||||
import MessageBoxConfig from "./MessageBoxConfig";
|
||||
|
||||
interface Props {
|
||||
storySettings: StorySettingsData;
|
||||
onInitValues: (values: StorySettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class MessageBoxConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.storySettings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <MessageBoxConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
storySettings: graphql`
|
||||
fragment MessageBoxConfigContainer_storySettings on StorySettings {
|
||||
messageBox {
|
||||
enabled
|
||||
content
|
||||
icon
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(MessageBoxConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -1,4 +0,0 @@
|
||||
export {
|
||||
default,
|
||||
default as MessageBoxConfigContainer,
|
||||
} from "./MessageBoxConfigContainer";
|
||||
+11
-3
@@ -1,9 +1,17 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import ToggleConfig from "../ToggleConfig";
|
||||
import WidthLimitedDescription from "../WidthLimitedDescription";
|
||||
import ToggleConfig from "./ToggleConfig";
|
||||
import WidthLimitedDescription from "./WidthLimitedDescription";
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
graphql`
|
||||
fragment PremodConfig_formValues on StorySettings {
|
||||
moderation
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
@@ -21,9 +29,9 @@ const PremodConfig: FunctionComponent<Props> = ({ disabled }) => (
|
||||
<Field name="moderation" type="checkbox" parse={parse} format={format}>
|
||||
{({ input }) => (
|
||||
<ToggleConfig
|
||||
{...input}
|
||||
id={input.name}
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
title={
|
||||
<Localized id="configure-premod-title">
|
||||
<span>Pre-Moderation</span>
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
|
||||
import { PremodConfigContainer_storySettings as StorySettingsData } from "coral-stream/__generated__/PremodConfigContainer_storySettings.graphql";
|
||||
|
||||
import PremodConfig from "./PremodConfig";
|
||||
|
||||
interface Props {
|
||||
storySettings: StorySettingsData;
|
||||
onInitValues: (values: StorySettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class PremodConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.storySettings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <PremodConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
storySettings: graphql`
|
||||
fragment PremodConfigContainer_storySettings on StorySettings {
|
||||
moderation
|
||||
}
|
||||
`,
|
||||
})(PremodConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -1,4 +0,0 @@
|
||||
export {
|
||||
default,
|
||||
default as PremodConfigContainer,
|
||||
} from "./PremodConfigContainer";
|
||||
+11
-3
@@ -1,11 +1,19 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { parseBool } from "coral-framework/lib/form";
|
||||
|
||||
import ToggleConfig from "../ToggleConfig";
|
||||
import WidthLimitedDescription from "../WidthLimitedDescription";
|
||||
import ToggleConfig from "./ToggleConfig";
|
||||
import WidthLimitedDescription from "./WidthLimitedDescription";
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
graphql`
|
||||
fragment PremodLinksConfig_formValues on StorySettings {
|
||||
premodLinksEnable
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
@@ -15,8 +23,8 @@ const PremodLinksConfig: FunctionComponent<Props> = ({ disabled }) => (
|
||||
<Field name="premodLinksEnable" type="checkbox" parse={parseBool}>
|
||||
{({ input }) => (
|
||||
<ToggleConfig
|
||||
id={input.name}
|
||||
{...input}
|
||||
id={input.name}
|
||||
disabled={disabled}
|
||||
title={
|
||||
<Localized id="configure-premodLink-title">
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
|
||||
import { PremodLinksConfigContainer_storySettings as StorySettingsData } from "coral-stream/__generated__/PremodLinksConfigContainer_storySettings.graphql";
|
||||
|
||||
import PremodLinksConfig from "./PremodLinksConfig";
|
||||
|
||||
interface Props {
|
||||
storySettings: StorySettingsData;
|
||||
onInitValues: (values: StorySettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class PremodLinksConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.storySettings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <PremodLinksConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
storySettings: graphql`
|
||||
fragment PremodLinksConfigContainer_storySettings on StorySettings {
|
||||
premodLinksEnable
|
||||
}
|
||||
`,
|
||||
})(PremodLinksConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -1,4 +0,0 @@
|
||||
export {
|
||||
default,
|
||||
default as PremodLinksConfigContainer,
|
||||
} from "./PremodLinksConfigContainer";
|
||||
@@ -123,7 +123,6 @@ const changeEmailContainer: FunctionComponent<Props> = ({
|
||||
};
|
||||
}
|
||||
|
||||
form.reset();
|
||||
setShowEditForm(false);
|
||||
|
||||
return;
|
||||
@@ -147,7 +146,7 @@ const changeEmailContainer: FunctionComponent<Props> = ({
|
||||
|
||||
const preventSubmit = (
|
||||
formState: Pick<
|
||||
FormState,
|
||||
FormState<any>,
|
||||
| "pristine"
|
||||
| "hasSubmitErrors"
|
||||
| "hasValidationErrors"
|
||||
@@ -348,12 +347,12 @@ const changeEmailContainer: FunctionComponent<Props> = ({
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
<PasswordField
|
||||
{...input}
|
||||
id={input.name}
|
||||
placeholder="Password"
|
||||
color={colorFromMeta(meta)}
|
||||
disabled={submitting}
|
||||
fullWidth
|
||||
{...input}
|
||||
/>
|
||||
</Localized>
|
||||
<FieldValidationMessage meta={meta} fullWidth />
|
||||
|
||||
@@ -59,7 +59,7 @@ const ChangePassword: FunctionComponent<Props> = ({ onResetPassword }) => {
|
||||
}
|
||||
|
||||
// Reset the form now that we're done.
|
||||
form.reset();
|
||||
form.initialize({});
|
||||
|
||||
return;
|
||||
},
|
||||
@@ -135,12 +135,12 @@ const ChangePassword: FunctionComponent<Props> = ({ onResetPassword }) => {
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<PasswordField
|
||||
{...input}
|
||||
fullWidth
|
||||
id={input.name}
|
||||
disabled={submitting}
|
||||
color={colorFromMeta(meta)}
|
||||
autoComplete="current-password"
|
||||
{...input}
|
||||
/>
|
||||
<FieldValidationMessage fullWidth meta={meta} />
|
||||
|
||||
@@ -173,12 +173,12 @@ const ChangePassword: FunctionComponent<Props> = ({ onResetPassword }) => {
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<PasswordField
|
||||
{...input}
|
||||
fullWidth
|
||||
id={input.name}
|
||||
disabled={submitting}
|
||||
color={colorFromMeta(meta)}
|
||||
autoComplete="new-password"
|
||||
{...input}
|
||||
/>
|
||||
<FieldValidationMessage fullWidth meta={meta} />
|
||||
</FormField>
|
||||
|
||||
+1
-2
@@ -144,7 +144,6 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
|
||||
};
|
||||
}
|
||||
|
||||
form.reset();
|
||||
setShowEditForm(false);
|
||||
setShowSuccessMessage(true);
|
||||
|
||||
@@ -299,8 +298,8 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
fullWidth
|
||||
{...input}
|
||||
fullWidth
|
||||
id="profile-changeUsername-username-confirm"
|
||||
/>
|
||||
<FieldValidationMessage meta={meta} />
|
||||
|
||||
@@ -45,7 +45,7 @@ interface FormProps {
|
||||
|
||||
const preventSubmit = (
|
||||
state: Pick<
|
||||
FormState,
|
||||
FormState<any>,
|
||||
| "pristine"
|
||||
| "hasSubmitErrors"
|
||||
| "hasValidationErrors"
|
||||
@@ -79,9 +79,8 @@ const ConfirmPage: FunctionComponent<Props> = ({
|
||||
};
|
||||
}
|
||||
|
||||
form.reset();
|
||||
|
||||
onProceed();
|
||||
|
||||
return;
|
||||
},
|
||||
[onProceed]
|
||||
@@ -169,13 +168,13 @@ const ConfirmPage: FunctionComponent<Props> = ({
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<TextField
|
||||
{...input}
|
||||
fullWidth
|
||||
id={input.name}
|
||||
data-testid="confirm-page-confirmation"
|
||||
disabled={submitting}
|
||||
color={colorFromMeta(meta)}
|
||||
autoComplete="off"
|
||||
{...input}
|
||||
/>
|
||||
<FieldValidationMessage fullWidth meta={meta} />
|
||||
</FormField>
|
||||
@@ -194,13 +193,13 @@ const ConfirmPage: FunctionComponent<Props> = ({
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<PasswordField
|
||||
{...input}
|
||||
fullWidth
|
||||
id={input.name}
|
||||
data-testid="confirm-page-password"
|
||||
disabled={submitting}
|
||||
color={colorFromMeta(meta)}
|
||||
autoComplete="off"
|
||||
{...input}
|
||||
/>
|
||||
<FieldValidationMessage fullWidth meta={meta} />
|
||||
</FormField>
|
||||
|
||||
@@ -88,7 +88,7 @@ const NotificationSettingsContainer: FunctionComponent<Props> = ({
|
||||
<Field name="onReply" type="checkbox">
|
||||
{({ input }) => (
|
||||
<Localized id="profile-account-notifications-onReply">
|
||||
<CheckBox id={input.name} {...input}>
|
||||
<CheckBox {...input} id={input.name}>
|
||||
My comment receives a reply
|
||||
</CheckBox>
|
||||
</Localized>
|
||||
@@ -99,7 +99,7 @@ const NotificationSettingsContainer: FunctionComponent<Props> = ({
|
||||
<Field name="onFeatured" type="checkbox">
|
||||
{({ input }) => (
|
||||
<Localized id="profile-account-notifications-onFeatured">
|
||||
<CheckBox id={input.name} {...input}>
|
||||
<CheckBox {...input} id={input.name}>
|
||||
My comment is featured
|
||||
</CheckBox>
|
||||
</Localized>
|
||||
@@ -110,7 +110,7 @@ const NotificationSettingsContainer: FunctionComponent<Props> = ({
|
||||
<Field name="onStaffReplies" type="checkbox">
|
||||
{({ input }) => (
|
||||
<Localized id="profile-account-notifications-onStaffReplies">
|
||||
<CheckBox id={input.name} {...input}>
|
||||
<CheckBox {...input} id={input.name}>
|
||||
A staff member replies to my comment
|
||||
</CheckBox>
|
||||
</Localized>
|
||||
@@ -121,7 +121,7 @@ const NotificationSettingsContainer: FunctionComponent<Props> = ({
|
||||
<Field name="onModeration" type="checkbox">
|
||||
{({ input }) => (
|
||||
<Localized id="profile-account-notifications-onModeration">
|
||||
<CheckBox id={input.name} {...input}>
|
||||
<CheckBox {...input} id={input.name}>
|
||||
My pending comment has been reviewed
|
||||
</CheckBox>
|
||||
</Localized>
|
||||
@@ -143,9 +143,8 @@ const NotificationSettingsContainer: FunctionComponent<Props> = ({
|
||||
<Field name="digestFrequency">
|
||||
{({ input }) => (
|
||||
<SelectField
|
||||
id={input.name}
|
||||
aria-label=""
|
||||
{...input}
|
||||
id={input.name}
|
||||
disabled={
|
||||
!values.onReply &&
|
||||
!values.onStaffReplies &&
|
||||
|
||||
+12
-7
@@ -2,6 +2,7 @@ import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import {
|
||||
act,
|
||||
createSinonStub,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -107,15 +108,19 @@ it("shows more of this conversation", async () => {
|
||||
);
|
||||
|
||||
// Show hidden comments.
|
||||
within(conversationThread)
|
||||
.getByText("Show More of This Conversation")
|
||||
.props.onClick();
|
||||
act(() => {
|
||||
within(conversationThread)
|
||||
.getByText("Show More of This Conversation")
|
||||
.props.onClick();
|
||||
});
|
||||
|
||||
// Wait until button disappears.
|
||||
await wait(() =>
|
||||
expect(
|
||||
within(conversationThread).queryByText("Show More of This Conversation")
|
||||
).toBeNull()
|
||||
await act(() =>
|
||||
wait(() =>
|
||||
expect(
|
||||
within(conversationThread).queryByText("Show More of This Conversation")
|
||||
).toBeNull()
|
||||
)
|
||||
);
|
||||
|
||||
expect(within(conversationThread).toJSON()).toMatchSnapshot();
|
||||
|
||||
@@ -3,6 +3,7 @@ import RTE from "@coralproject/rte";
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
findParentWithType,
|
||||
@@ -125,15 +126,19 @@ it("post a reply", async () => {
|
||||
});
|
||||
|
||||
// Write reply .
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
form.props.onSubmit();
|
||||
act(() => rte.props.onChange({ html: "<b>Hello world!</b>" }));
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
const commentReplyList = within(testRenderer.root).getByTestID(
|
||||
"commentReplyList-comment-0"
|
||||
);
|
||||
|
||||
// Test after server response.
|
||||
await waitForElement(() =>
|
||||
within(commentReplyList).getByText("(from server)", { exact: false })
|
||||
);
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(commentReplyList).getByText("(from server)", { exact: false })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ERROR_CODES } from "coral-common/errors";
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
findParentWithType,
|
||||
waitForElement,
|
||||
@@ -90,8 +91,10 @@ it("validate min", async () => {
|
||||
|
||||
const text = "Please enter at least 3 characters.";
|
||||
|
||||
rte.props.onChange({ html: "ab" });
|
||||
form.props.onSubmit();
|
||||
act(() => rte.props.onChange({ html: "ab" }));
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
within(form).getByText(text);
|
||||
});
|
||||
|
||||
@@ -100,17 +103,19 @@ it("validate max", async () => {
|
||||
|
||||
const text = "Please enter at max 10 characters.";
|
||||
|
||||
rte.props.onChange({ html: "abcdefghijklmnopqrst" });
|
||||
form.props.onSubmit();
|
||||
act(() => rte.props.onChange({ html: "abcdefghijklmnopqrst" }));
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
within(form).getByText(text);
|
||||
});
|
||||
|
||||
it("show remaining characters", async () => {
|
||||
const { rte, form } = await createTestRenderer();
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
act(() => rte.props.onChange({ html: "abc" }));
|
||||
within(form).getByText("7 characters remaining");
|
||||
rte.props.onChange({ html: "abcdefghijkl" });
|
||||
act(() => rte.props.onChange({ html: "abcdefghijkl" }));
|
||||
within(form).getByText("-2 characters remaining");
|
||||
});
|
||||
|
||||
@@ -150,17 +155,21 @@ it("update from server upon specific char count error", async () => {
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
act(() => rte.props.onChange({ html: "abc" }));
|
||||
within(form).getByText("7 characters remaining");
|
||||
rte.props.onChange({ html: "abcdefgh" });
|
||||
act(() => rte.props.onChange({ html: "abcdefgh" }));
|
||||
within(form).getByText("2 characters remaining");
|
||||
form.props.onSubmit();
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("-3 characters remaining")
|
||||
);
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("-3 characters remaining")
|
||||
);
|
||||
});
|
||||
// Body submit error should be displayed.
|
||||
within(form).getByText(errorCode);
|
||||
rte.props.onChange({ html: "abcde" });
|
||||
act(() => rte.props.onChange({ html: "abcde" }));
|
||||
|
||||
// Body submit error should disappear when form gets dirty.
|
||||
expect(within(form).queryByText(errorCode)).toBeNull();
|
||||
|
||||
@@ -5,8 +5,10 @@ import { ERROR_CODES } from "coral-common/errors";
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
findParentWithType,
|
||||
wait,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
@@ -71,16 +73,22 @@ it("validate min", async () => {
|
||||
|
||||
const text = "Please enter at least 3 characters.";
|
||||
|
||||
rte.props.onChange({ html: "ab" });
|
||||
form.props.onSubmit();
|
||||
within(form).getByText(text);
|
||||
act(() => rte.props.onChange({ html: "ab" }));
|
||||
act(() => form.props.onSubmit());
|
||||
await act(async () => {
|
||||
waitForElement(() => within(form).getByText(text));
|
||||
});
|
||||
|
||||
// Reset validation when erasing all content.
|
||||
rte.props.onChange({ html: "" });
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
act(() => rte.props.onChange({ html: "" }));
|
||||
await wait(() => {
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
});
|
||||
|
||||
rte.props.onChange({ html: "ab" });
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
act(() => rte.props.onChange({ html: "ab" }));
|
||||
await wait(() => {
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("validate max", async () => {
|
||||
@@ -88,24 +96,32 @@ it("validate max", async () => {
|
||||
|
||||
const text = "Please enter at max 10 characters.";
|
||||
|
||||
rte.props.onChange({ html: "abcdefghijklmnopqrst" });
|
||||
form.props.onSubmit();
|
||||
within(form).getByText(text);
|
||||
act(() => rte.props.onChange({ html: "abcdefghijklmnopqrst" }));
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
await act(async () => {
|
||||
await waitForElement(() => within(form).queryByText(text));
|
||||
});
|
||||
|
||||
// Reset validation when erasing all content.
|
||||
rte.props.onChange({ html: "" });
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
act(() => rte.props.onChange({ html: "" }));
|
||||
await wait(() => {
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
});
|
||||
|
||||
rte.props.onChange({ html: "abcdefghijklmnopqrst" });
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
act(() => rte.props.onChange({ html: "abcdefghijklmnopqrst" }));
|
||||
await wait(() => {
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("show remaining characters", async () => {
|
||||
const { rte, form } = await createTestRenderer();
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
act(() => rte.props.onChange({ html: "abc" }));
|
||||
within(form).getByText("7 characters remaining");
|
||||
rte.props.onChange({ html: "abcdefghijkl" });
|
||||
act(() => rte.props.onChange({ html: "abcdefghijkl" }));
|
||||
within(form).getByText("-2 characters remaining");
|
||||
});
|
||||
|
||||
@@ -145,19 +161,27 @@ it("update from server upon specific char count error", async () => {
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
within(form).getByText("7 characters remaining");
|
||||
rte.props.onChange({ html: "abcdefgh" });
|
||||
within(form).getByText("2 characters remaining");
|
||||
form.props.onSubmit();
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("-3 characters remaining")
|
||||
);
|
||||
// Body submit error should be displayed.
|
||||
within(form).getByText(errorCode);
|
||||
rte.props.onChange({ html: "abcde" });
|
||||
act(() => rte.props.onChange({ html: "abc" }));
|
||||
waitForElement(() => within(form).getByText("7 characters remaining"));
|
||||
|
||||
// Body submit error should disappear when form gets dirty.
|
||||
expect(within(form).queryByText(errorCode)).toBeNull();
|
||||
act(() => rte.props.onChange({ html: "abcdefgh" }));
|
||||
waitForElement(() => within(form).getByText("2 characters remaining"));
|
||||
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("-3 characters remaining")
|
||||
);
|
||||
});
|
||||
// Body submit error should be displayed.
|
||||
await waitForElement(() => within(form).getByText(errorCode));
|
||||
|
||||
act(() => rte.props.onChange({ html: "abcde" }));
|
||||
await wait(() => {
|
||||
// Body submit error should disappear when form gets dirty.
|
||||
expect(within(form).queryByText(errorCode)).toBeNull();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ERROR_CODES } from "coral-common/errors";
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
findParentWithType,
|
||||
waitForElement,
|
||||
@@ -82,15 +83,15 @@ it("validate min", async () => {
|
||||
|
||||
const text = "Please enter at least 3 characters.";
|
||||
|
||||
rte.props.onChange({ html: "ab" });
|
||||
form.props.onSubmit();
|
||||
act(() => rte.props.onChange({ html: "ab" }));
|
||||
act(() => form.props.onSubmit());
|
||||
within(form).getByText(text);
|
||||
|
||||
// Reset validation when erasing all content.
|
||||
rte.props.onChange({ html: "" });
|
||||
act(() => rte.props.onChange({ html: "" }));
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
|
||||
rte.props.onChange({ html: "ab" });
|
||||
act(() => rte.props.onChange({ html: "ab" }));
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
});
|
||||
|
||||
@@ -99,25 +100,26 @@ it("validate max", async () => {
|
||||
|
||||
const text = "Please enter at max 10 characters.";
|
||||
|
||||
rte.props.onChange({ html: "abcdefghijklmnopqrst" });
|
||||
form.props.onSubmit();
|
||||
act(() => rte.props.onChange({ html: "abcdefghijklmnopqrst" }));
|
||||
act(() => form.props.onSubmit());
|
||||
within(form).getByText(text);
|
||||
|
||||
// Reset validation when erasing all content.
|
||||
rte.props.onChange({ html: "" });
|
||||
act(() => rte.props.onChange({ html: "" }));
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
|
||||
rte.props.onChange({ html: "abcdefghijklmnopqrst" });
|
||||
act(() => rte.props.onChange({ html: "abcdefghijklmnopqrst" }));
|
||||
expect(within(form).queryByText(text)).toBeNull();
|
||||
});
|
||||
|
||||
it("show remaining characters", async () => {
|
||||
const { rte, form } = await createTestRenderer();
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
within(form).getByText("7 characters remaining");
|
||||
rte.props.onChange({ html: "abcdefghijkl" });
|
||||
within(form).getByText("-2 characters remaining");
|
||||
act(() => rte.props.onChange({ html: "abc" }));
|
||||
waitForElement(() => within(form).getByText("7 characters remaining"));
|
||||
|
||||
act(() => rte.props.onChange({ html: "abcdefghijkl" }));
|
||||
waitForElement(() => within(form).getByText("-2 characters remaining"));
|
||||
});
|
||||
|
||||
it("update from server upon specific char count error", async () => {
|
||||
@@ -156,17 +158,19 @@ it("update from server upon specific char count error", async () => {
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
within(form).getByText("7 characters remaining");
|
||||
rte.props.onChange({ html: "abcdefgh" });
|
||||
within(form).getByText("2 characters remaining");
|
||||
form.props.onSubmit();
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("-3 characters remaining")
|
||||
);
|
||||
act(() => rte.props.onChange({ html: "abc" }));
|
||||
waitForElement(() => within(form).getByText("7 characters remaining"));
|
||||
|
||||
act(() => rte.props.onChange({ html: "abcdefgh" }));
|
||||
waitForElement(() => within(form).getByText("2 characters remaining"));
|
||||
|
||||
await act(async () => form.props.onSubmit());
|
||||
waitForElement(() => within(form).getByText("-3 characters remaining"));
|
||||
|
||||
// Body submit error should be displayed.
|
||||
within(form).getByText(errorCode);
|
||||
rte.props.onChange({ html: "abcde" });
|
||||
waitForElement(() => within(form).getByText(errorCode));
|
||||
|
||||
act(() => rte.props.onChange({ html: "abcde" }));
|
||||
|
||||
// Body submit error should disappear when form gets dirty.
|
||||
expect(within(form).queryByText(errorCode)).toBeNull();
|
||||
|
||||
@@ -4,6 +4,7 @@ import timekeeper from "timekeeper";
|
||||
import { ERROR_CODES } from "coral-common/errors";
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import {
|
||||
act,
|
||||
createSinonStub,
|
||||
waitForElement,
|
||||
within,
|
||||
@@ -74,12 +75,15 @@ function createTestRenderer(
|
||||
return testRenderer;
|
||||
}
|
||||
|
||||
afterAll(() => {
|
||||
beforeEach(() => {
|
||||
timekeeper.freeze(commentWithReplies.createdAt);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
timekeeper.reset();
|
||||
});
|
||||
|
||||
it("edit a comment", async () => {
|
||||
timekeeper.freeze(commentWithReplies.createdAt);
|
||||
const testRenderer = createTestRenderer();
|
||||
|
||||
const comment = await waitForElement(() =>
|
||||
@@ -90,28 +94,36 @@ it("edit a comment", async () => {
|
||||
);
|
||||
|
||||
// Open edit form.
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick();
|
||||
act(() =>
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick()
|
||||
);
|
||||
expect(within(comment).toJSON()).toMatchSnapshot("edit form");
|
||||
|
||||
testRenderer.root
|
||||
.findByProps({
|
||||
inputId: `comments-editCommentForm-rte-${commentWithReplies.id}`,
|
||||
})
|
||||
.props.onChange({ html: "Edited!" });
|
||||
act(() =>
|
||||
testRenderer.root
|
||||
.findByProps({
|
||||
inputId: `comments-editCommentForm-rte-${commentWithReplies.id}`,
|
||||
})
|
||||
.props.onChange({ html: "Edited!" })
|
||||
);
|
||||
|
||||
within(comment)
|
||||
.getByType("form")
|
||||
.props.onSubmit();
|
||||
act(() => {
|
||||
within(comment)
|
||||
.getByType("form")
|
||||
.props.onSubmit();
|
||||
});
|
||||
|
||||
// Test optimistic response.
|
||||
expect(within(comment).toJSON()).toMatchSnapshot("optimistic response");
|
||||
|
||||
// Wait for server response.
|
||||
await waitForElement(() =>
|
||||
within(comment).getByText("Edited! (from server)")
|
||||
);
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(comment).getByText("Edited! (from server)")
|
||||
);
|
||||
});
|
||||
|
||||
// Test after server response.
|
||||
expect(within(comment).toJSON()).toMatchSnapshot("server response");
|
||||
@@ -125,25 +137,31 @@ it("edit a comment and handle non-published comment state", async () => {
|
||||
);
|
||||
|
||||
// Open edit form.
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick();
|
||||
act(() =>
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick()
|
||||
);
|
||||
|
||||
testRenderer.root
|
||||
.findByProps({
|
||||
inputId: `comments-editCommentForm-rte-${commentWithReplies.id}`,
|
||||
})
|
||||
.props.onChange({ html: "Edited!" });
|
||||
act(() =>
|
||||
testRenderer.root
|
||||
.findByProps({
|
||||
inputId: `comments-editCommentForm-rte-${commentWithReplies.id}`,
|
||||
})
|
||||
.props.onChange({ html: "Edited!" })
|
||||
);
|
||||
|
||||
within(comment)
|
||||
.getByType("form")
|
||||
.props.onSubmit();
|
||||
act(() => {
|
||||
within(comment)
|
||||
.getByType("form")
|
||||
.props.onSubmit();
|
||||
});
|
||||
|
||||
// Test after server response.
|
||||
await waitForElement(() =>
|
||||
within(comment).getByText("will be reviewed", { exact: false })
|
||||
);
|
||||
await within(comment)
|
||||
within(comment)
|
||||
.getByText("Dismiss")
|
||||
.props.onClick();
|
||||
expect(
|
||||
@@ -166,7 +184,6 @@ it("edit a comment and handle non-published comment state", async () => {
|
||||
});
|
||||
|
||||
it("cancel edit", async () => {
|
||||
timekeeper.freeze(commentWithReplies.createdAt);
|
||||
const testRenderer = createTestRenderer();
|
||||
|
||||
const comment = await waitForElement(() =>
|
||||
@@ -174,20 +191,23 @@ it("cancel edit", async () => {
|
||||
);
|
||||
|
||||
// Open edit form.
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick();
|
||||
act(() =>
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick()
|
||||
);
|
||||
|
||||
// Cancel edit form.
|
||||
within(comment)
|
||||
.getByText("Cancel")
|
||||
.props.onClick();
|
||||
act(() =>
|
||||
within(comment)
|
||||
.getByText("Cancel")
|
||||
.props.onClick()
|
||||
);
|
||||
|
||||
expect(within(comment).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows expiry message", async () => {
|
||||
timekeeper.freeze(commentWithReplies.createdAt);
|
||||
const testRenderer = createTestRenderer();
|
||||
|
||||
const comment = await waitForElement(() =>
|
||||
@@ -197,9 +217,11 @@ it("shows expiry message", async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
// Open edit form.
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick();
|
||||
act(() =>
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick()
|
||||
);
|
||||
|
||||
timekeeper.reset();
|
||||
jest.runOnlyPendingTimers();
|
||||
@@ -208,14 +230,15 @@ it("shows expiry message", async () => {
|
||||
expect(within(comment).toJSON()).toMatchSnapshot("edit time expired");
|
||||
|
||||
// Close edit form.
|
||||
within(comment)
|
||||
.getByText("Close")
|
||||
.props.onClick();
|
||||
act(() =>
|
||||
within(comment)
|
||||
.getByText("Close")
|
||||
.props.onClick()
|
||||
);
|
||||
expect(within(comment).toJSON()).toMatchSnapshot("edit form closed");
|
||||
});
|
||||
|
||||
it("edit a comment and handle server error", async () => {
|
||||
timekeeper.freeze(commentWithReplies.createdAt);
|
||||
const testRenderer = createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
@@ -232,21 +255,29 @@ it("edit a comment and handle server error", async () => {
|
||||
);
|
||||
|
||||
// Open edit form.
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick();
|
||||
act(() =>
|
||||
within(comment)
|
||||
.getByText("Edit")
|
||||
.props.onClick()
|
||||
);
|
||||
expect(within(comment).toJSON()).toMatchSnapshot("edit form");
|
||||
|
||||
testRenderer.root
|
||||
.findByProps({
|
||||
inputId: `comments-editCommentForm-rte-${commentWithReplies.id}`,
|
||||
})
|
||||
.props.onChange({ html: "Edited!" });
|
||||
act(() =>
|
||||
testRenderer.root
|
||||
.findByProps({
|
||||
inputId: `comments-editCommentForm-rte-${commentWithReplies.id}`,
|
||||
})
|
||||
.props.onChange({ html: "Edited!" })
|
||||
);
|
||||
|
||||
within(comment)
|
||||
.getByType("form")
|
||||
.props.onSubmit();
|
||||
act(() => {
|
||||
within(comment)
|
||||
.getByType("form")
|
||||
.props.onSubmit();
|
||||
});
|
||||
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(comment).getByText("INTERNAL_ERROR"));
|
||||
await act(async () => {
|
||||
await waitForElement(() => within(comment).getByText("INTERNAL_ERROR"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,90 +74,95 @@ async function createTestRenderer(
|
||||
}
|
||||
|
||||
it("post a comment", async () => {
|
||||
const { rte, form, tabPane } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
},
|
||||
});
|
||||
return {
|
||||
edge: {
|
||||
cursor: "",
|
||||
node: {
|
||||
...baseComment,
|
||||
id: "comment-x",
|
||||
author: commenters[0],
|
||||
body: "<b>Hello world! (from server)</b>",
|
||||
await act(async () => {
|
||||
const { rte, form, tabPane } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
},
|
||||
},
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
return {
|
||||
edge: {
|
||||
cursor: "",
|
||||
node: {
|
||||
...baseComment,
|
||||
id: "comment-x",
|
||||
author: commenters[0],
|
||||
body: "<b>Hello world! (from server)</b>",
|
||||
},
|
||||
},
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
|
||||
timekeeper.freeze(new Date(baseComment.createdAt));
|
||||
form.props.onSubmit();
|
||||
timekeeper.reset();
|
||||
|
||||
// Test optimistic response.
|
||||
expect(
|
||||
within(within(tabPane).queryAllByTestID(/^comment-/)[0]).toJSON()
|
||||
).toMatchSnapshot("optimistic response");
|
||||
|
||||
// Test for server response.
|
||||
await waitForElement(() =>
|
||||
within(within(tabPane).queryAllByTestID(/^comment-/)[0]).getByText(
|
||||
"<b>Hello world! (from server)</b>"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
|
||||
timekeeper.freeze(new Date(baseComment.createdAt));
|
||||
form.props.onSubmit();
|
||||
timekeeper.reset();
|
||||
|
||||
// Test optimistic response.
|
||||
expect(
|
||||
within(within(tabPane).queryAllByTestID(/^comment-/)[0]).toJSON()
|
||||
).toMatchSnapshot("optimistic response");
|
||||
|
||||
// Test for server response.
|
||||
await waitForElement(() =>
|
||||
within(within(tabPane).queryAllByTestID(/^comment-/)[0]).getByText(
|
||||
"<b>Hello world! (from server)</b>"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const postACommentAndHandleNonPublishedComment = async (
|
||||
dismiss: (form: ReactTestInstance, rte: ReactTestInstance) => void
|
||||
) => {
|
||||
const { rte, form } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
},
|
||||
});
|
||||
return {
|
||||
edge: {
|
||||
cursor: "",
|
||||
node: {
|
||||
...baseComment,
|
||||
id: "comment-x",
|
||||
status: "SYSTEM_WITHHELD",
|
||||
author: commenters[0],
|
||||
await act(async () => {
|
||||
const { rte, form } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
},
|
||||
},
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
return {
|
||||
edge: {
|
||||
cursor: "",
|
||||
node: {
|
||||
...baseComment,
|
||||
id: "comment-x",
|
||||
status: "SYSTEM_WITHHELD",
|
||||
author: commenters[0],
|
||||
body: "<b>Hello world!</b>",
|
||||
},
|
||||
},
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
form.props.onSubmit();
|
||||
|
||||
// Test after server response.
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("will be reviewed", { exact: false })
|
||||
);
|
||||
|
||||
dismiss(form, rte);
|
||||
expect(
|
||||
within(form).queryByText("will be reviewed", { exact: false })
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
form.props.onSubmit();
|
||||
|
||||
// Test after server response.
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("will be reviewed", { exact: false })
|
||||
);
|
||||
dismiss(form, rte);
|
||||
expect(
|
||||
within(form).queryByText("will be reviewed", { exact: false })
|
||||
).toBeNull();
|
||||
};
|
||||
|
||||
it("post a comment and handle non-visible comment state (dismiss by click)", async () =>
|
||||
@@ -173,150 +178,157 @@ it("post a comment and handle non-visible comment state (dismiss by typing)", as
|
||||
}));
|
||||
|
||||
it("post a comment and handle server error", async () => {
|
||||
const { form, rte } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake(() => {
|
||||
throw new InvalidRequestError({ code: ERROR_CODES.INTERNAL_ERROR });
|
||||
}),
|
||||
await act(async () => {
|
||||
const { form, rte } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake(() => {
|
||||
throw new InvalidRequestError({ code: ERROR_CODES.INTERNAL_ERROR });
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
form.props.onSubmit();
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
form.props.onSubmit();
|
||||
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(form).getByText("INTERNAL_ERROR"));
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(form).getByText("INTERNAL_ERROR"));
|
||||
});
|
||||
});
|
||||
|
||||
it("handle moderation nudge error", async () => {
|
||||
const { form, rte } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createComment: createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
nudge: true,
|
||||
},
|
||||
});
|
||||
throw new ModerationNudgeError({
|
||||
code: ERROR_CODES.TOXIC_COMMENT,
|
||||
});
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
nudge: false,
|
||||
},
|
||||
});
|
||||
return {
|
||||
edge: {
|
||||
cursor: "",
|
||||
node: {
|
||||
...baseComment,
|
||||
id: "comment-x",
|
||||
status: "SYSTEM_WITHHELD",
|
||||
author: commenters[0],
|
||||
await act(async () => {
|
||||
const { form, rte } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createComment: createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
nudge: true,
|
||||
},
|
||||
},
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
),
|
||||
});
|
||||
throw new ModerationNudgeError({
|
||||
code: ERROR_CODES.TOXIC_COMMENT,
|
||||
});
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
nudge: false,
|
||||
},
|
||||
});
|
||||
return {
|
||||
edge: {
|
||||
cursor: "",
|
||||
node: {
|
||||
...baseComment,
|
||||
id: "comment-x",
|
||||
status: "SYSTEM_WITHHELD",
|
||||
author: commenters[0],
|
||||
body: "<b>Hello world!</b>",
|
||||
},
|
||||
},
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
form.props.onSubmit();
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
form.props.onSubmit();
|
||||
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(form).getByText("TOXIC_COMMENT"));
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(form).getByText("TOXIC_COMMENT"));
|
||||
|
||||
// Try again, now nudging should be disabled.
|
||||
form.props.onSubmit();
|
||||
// Try again, now nudging should be disabled.
|
||||
form.props.onSubmit();
|
||||
|
||||
// Comment should now go to moderation.
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("will be reviewed", { exact: false })
|
||||
);
|
||||
// Comment should now go to moderation.
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("will be reviewed", { exact: false })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("handle disabled commenting error", async () => {
|
||||
let createCommentCalled = false;
|
||||
const { rte, form } = await createTestRenderer(
|
||||
createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
createComment: () => {
|
||||
createCommentCalled = true;
|
||||
throw new InvalidRequestError({
|
||||
code: ERROR_CODES.COMMENTING_DISABLED,
|
||||
});
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
settings: () => {
|
||||
if (!createCommentCalled) {
|
||||
return settings;
|
||||
}
|
||||
return {
|
||||
...settings,
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
message: "commenting disabled",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
let createCommentCalled = false;
|
||||
const { rte, form } = await createTestRenderer(
|
||||
createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
createComment: () => {
|
||||
createCommentCalled = true;
|
||||
throw new InvalidRequestError({
|
||||
code: ERROR_CODES.COMMENTING_DISABLED,
|
||||
});
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
settings: () => {
|
||||
if (!createCommentCalled) {
|
||||
return settings;
|
||||
}
|
||||
return {
|
||||
...settings,
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
message: "commenting disabled",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
form.props.onSubmit();
|
||||
await waitForElement(() => within(form).getByText("commenting disabled"));
|
||||
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
});
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it("handle story closed", async () => {
|
||||
let returnStory = stories[0];
|
||||
const { rte, form } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake(() => {
|
||||
throw new InvalidRequestError({
|
||||
code: ERROR_CODES.STORY_CLOSED,
|
||||
});
|
||||
}),
|
||||
await act(async () => {
|
||||
let returnStory = stories[0];
|
||||
const { rte, form } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake(() => {
|
||||
throw new InvalidRequestError({
|
||||
code: ERROR_CODES.STORY_CLOSED,
|
||||
});
|
||||
}),
|
||||
},
|
||||
Query: {
|
||||
story: sinon.stub().callsFake(() => returnStory),
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
story: sinon.stub().callsFake(() => returnStory),
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
form.props.onSubmit();
|
||||
rte.props.onChange({ html: "abc" });
|
||||
form.props.onSubmit();
|
||||
|
||||
// Change the story that we return to be closed.
|
||||
returnStory = { ...stories[0], isClosed: true };
|
||||
// Change the story that we return to be closed.
|
||||
returnStory = { ...stories[0], isClosed: true };
|
||||
|
||||
await waitForElement(() => within(form).getByText("Story is closed"));
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
await waitForElement(() => within(form).getByText("Story is closed"));
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import sinon from "sinon";
|
||||
import timekeeper from "timekeeper";
|
||||
|
||||
import {
|
||||
act,
|
||||
createSinonStub,
|
||||
waitForElement,
|
||||
within,
|
||||
@@ -82,9 +83,11 @@ it("post a reply", async () => {
|
||||
);
|
||||
|
||||
// Open reply form.
|
||||
within(deepestReply)
|
||||
.getByText("Reply", { selector: "button" })
|
||||
.props.onClick();
|
||||
act(() =>
|
||||
within(deepestReply)
|
||||
.getByText("Reply", { selector: "button" })
|
||||
.props.onClick()
|
||||
);
|
||||
|
||||
const form = await waitForElement(() =>
|
||||
within(deepestReply).getByType("form")
|
||||
@@ -92,14 +95,18 @@ it("post a reply", async () => {
|
||||
expect(within(deepestReply).toJSON()).toMatchSnapshot("open reply form");
|
||||
|
||||
// Write reply .
|
||||
testRenderer.root
|
||||
.findByProps({
|
||||
inputId: "comments-replyCommentForm-rte-comment-with-deepest-replies-5",
|
||||
})
|
||||
.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
act(() =>
|
||||
testRenderer.root
|
||||
.findByProps({
|
||||
inputId: "comments-replyCommentForm-rte-comment-with-deepest-replies-5",
|
||||
})
|
||||
.props.onChange({ html: "<b>Hello world!</b>" })
|
||||
);
|
||||
|
||||
timekeeper.freeze(new Date(baseComment.createdAt));
|
||||
form.props.onSubmit();
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
const deepestReplyList = within(streamLog).getByTestID(
|
||||
"commentReplyList-comment-with-deepest-replies-5"
|
||||
@@ -112,7 +119,9 @@ it("post a reply", async () => {
|
||||
timekeeper.reset();
|
||||
|
||||
// Test after server response.
|
||||
await waitForElement(() =>
|
||||
within(deepestReplyList).getByText("(from server)", { exact: false })
|
||||
);
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(deepestReplyList).getByText("(from server)", { exact: false })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ModerationNudgeError,
|
||||
} from "coral-framework/lib/errors";
|
||||
import {
|
||||
act,
|
||||
createSinonStub,
|
||||
findParentWithType,
|
||||
waitForElement,
|
||||
@@ -53,7 +54,7 @@ async function createTestRenderer(
|
||||
selector: "button",
|
||||
});
|
||||
|
||||
replyButton.props.onClick();
|
||||
act(() => replyButton.props.onClick());
|
||||
|
||||
const rte = await waitForElement(
|
||||
() =>
|
||||
@@ -79,7 +80,7 @@ async function createTestRenderer(
|
||||
|
||||
it("hides form when reclicking on the reply button", async () => {
|
||||
const { comment, replyButton } = await createTestRenderer();
|
||||
replyButton.props.onClick();
|
||||
act(() => replyButton.props.onClick());
|
||||
expect(within(comment).queryByLabelText("Write a reply")).toBeNull();
|
||||
});
|
||||
|
||||
@@ -114,10 +115,12 @@ it("post a reply", async () => {
|
||||
expect(within(comment).toJSON()).toMatchSnapshot("open reply form");
|
||||
|
||||
// Write reply .
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
act(() => rte.props.onChange({ html: "<b>Hello world!</b>" }));
|
||||
|
||||
timekeeper.freeze(new Date(baseComment.createdAt));
|
||||
form.props.onSubmit();
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
const commentReplyList = within(testRenderer.root).getByTestID(
|
||||
"commentReplyList-comment-0"
|
||||
@@ -130,9 +133,11 @@ it("post a reply", async () => {
|
||||
timekeeper.reset();
|
||||
|
||||
// Test after server response.
|
||||
await waitForElement(() =>
|
||||
within(commentReplyList).getByText("(from server)", { exact: false })
|
||||
);
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(commentReplyList).getByText("(from server)", { exact: false })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("post a reply and handle non-visible comment state", async () => {
|
||||
@@ -165,15 +170,21 @@ it("post a reply and handle non-visible comment state", async () => {
|
||||
});
|
||||
|
||||
// Write reply .
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
form.props.onSubmit();
|
||||
act(() => rte.props.onChange({ html: "<b>Hello world!</b>" }));
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
// Test after server response.
|
||||
await waitForElement(() =>
|
||||
within(comment).getByText("will be reviewed", { exact: false })
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(comment).getByText("will be reviewed", { exact: false })
|
||||
);
|
||||
});
|
||||
act(() =>
|
||||
within(comment)
|
||||
.getByText("Dismiss")
|
||||
.props.onClick()
|
||||
);
|
||||
await within(comment)
|
||||
.getByText("Dismiss")
|
||||
.props.onClick();
|
||||
expect(
|
||||
within(comment).queryByText("will be reviewed", { exact: false })
|
||||
).toBeNull();
|
||||
@@ -192,12 +203,16 @@ it("post a reply and handle server error", async () => {
|
||||
);
|
||||
|
||||
// Write reply .
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
act(() => rte.props.onChange({ html: "<b>Hello world!</b>" }));
|
||||
|
||||
form.props.onSubmit();
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(comment).getByText("INTERNAL_ERROR"));
|
||||
await act(async () => {
|
||||
await waitForElement(() => within(comment).getByText("INTERNAL_ERROR"));
|
||||
});
|
||||
});
|
||||
|
||||
it("handle moderation nudge error", async () => {
|
||||
@@ -250,19 +265,27 @@ it("handle moderation nudge error", async () => {
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
form.props.onSubmit();
|
||||
act(() => rte.props.onChange({ html: "<b>Hello world!</b>" }));
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(form).getByText("TOXIC_COMMENT"));
|
||||
await act(async () => {
|
||||
await waitForElement(() => within(form).getByText("TOXIC_COMMENT"));
|
||||
});
|
||||
|
||||
// Try again, now nudging should be disabled.
|
||||
form.props.onSubmit();
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
// Comment should now go to moderation.
|
||||
await waitForElement(() =>
|
||||
within(comment).getByText("will be reviewed", { exact: false })
|
||||
);
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(comment).getByText("will be reviewed", { exact: false })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("handle disabled commenting error", async () => {
|
||||
@@ -283,8 +306,10 @@ it("handle disabled commenting error", async () => {
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
form.props.onSubmit();
|
||||
act(() => rte.props.onChange({ html: "abc" }));
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
// Change the settings that we return to be closed.
|
||||
returnSettings = {
|
||||
@@ -295,7 +320,9 @@ it("handle disabled commenting error", async () => {
|
||||
},
|
||||
};
|
||||
|
||||
await waitForElement(() => within(form).getByText("commenting disabled"));
|
||||
await act(async () => {
|
||||
await waitForElement(() => within(form).getByText("commenting disabled"));
|
||||
});
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
});
|
||||
@@ -318,13 +345,17 @@ it("handle story closed error", async () => {
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
form.props.onSubmit();
|
||||
act(() => rte.props.onChange({ html: "abc" }));
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
// Change the story that we return to be closed.
|
||||
returnStory = { ...stories[0], isClosed: true };
|
||||
|
||||
await waitForElement(() => within(form).getByText("Story is closed"));
|
||||
await act(async () => {
|
||||
await waitForElement(() => within(form).getByText("Story is closed"));
|
||||
});
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { ERROR_CODES } from "coral-common/errors";
|
||||
import { timeout } from "coral-common/utils";
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import { wait, waitForElement, within } from "coral-framework/testHelpers";
|
||||
import { act, wait, waitForElement, within } from "coral-framework/testHelpers";
|
||||
|
||||
import { commenters, settings, stories } from "../../fixtures";
|
||||
import create from "./create";
|
||||
@@ -78,7 +77,7 @@ it("render popup", async () => {
|
||||
within(testRenderer.root).getByTestID(`comment-${commentID}`)
|
||||
);
|
||||
const button = within(comment).getByText("Report", { selector: "button" });
|
||||
button.props.onClick();
|
||||
act(() => button.props.onClick());
|
||||
|
||||
const popover = within(testRenderer.root).getByID(
|
||||
button.props["aria-controls"]
|
||||
@@ -94,18 +93,17 @@ it("close popup", async () => {
|
||||
within(testRenderer.root).getByTestID(`comment-${commentID}`)
|
||||
);
|
||||
const button = within(comment).getByText("Report", { selector: "button" });
|
||||
button.props.onClick();
|
||||
act(() => button.props.onClick());
|
||||
|
||||
const popover = within(testRenderer.root).getByID(
|
||||
button.props["aria-controls"]
|
||||
);
|
||||
|
||||
// There is a once per frame click protection, so we wait a frame...
|
||||
await timeout();
|
||||
|
||||
within(popover)
|
||||
.getByLabelText("Close Popover", { exact: false })
|
||||
.props.onClick({});
|
||||
act(() =>
|
||||
within(popover)
|
||||
.getByLabelText("Close Popover", { exact: false })
|
||||
.props.onClick({})
|
||||
);
|
||||
expect(within(popover).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -116,7 +114,7 @@ it("render popup expanded", async () => {
|
||||
within(testRenderer.root).getByTestID(`comment-${commentID}`)
|
||||
);
|
||||
const button = within(comment).getByText("Report", { selector: "button" });
|
||||
button.props.onClick();
|
||||
act(() => button.props.onClick());
|
||||
|
||||
const popover = within(testRenderer.root).getByID(
|
||||
button.props["aria-controls"]
|
||||
@@ -126,9 +124,11 @@ it("render popup expanded", async () => {
|
||||
"This comment is offensive"
|
||||
);
|
||||
|
||||
radioButton.props.onChange({
|
||||
target: { type: "radio", value: radioButton.props.value },
|
||||
});
|
||||
act(() =>
|
||||
radioButton.props.onChange({
|
||||
target: { type: "radio", value: radioButton.props.value },
|
||||
})
|
||||
);
|
||||
|
||||
expect(within(popover).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -140,7 +140,7 @@ it("report comment as offensive", async () => {
|
||||
within(testRenderer.root).getByTestID(`comment-${commentID}`)
|
||||
);
|
||||
const button = within(comment).getByText("Report", { selector: "button" });
|
||||
button.props.onClick();
|
||||
act(() => button.props.onClick());
|
||||
|
||||
const popover = within(testRenderer.root).getByID(
|
||||
button.props["aria-controls"]
|
||||
@@ -150,21 +150,31 @@ it("report comment as offensive", async () => {
|
||||
"This comment is offensive"
|
||||
);
|
||||
|
||||
radioButton.props.onChange({
|
||||
target: { type: "radio", value: radioButton.props.value },
|
||||
act(() =>
|
||||
radioButton.props.onChange({
|
||||
target: { type: "radio", value: radioButton.props.value },
|
||||
})
|
||||
);
|
||||
|
||||
act(() =>
|
||||
within(popover)
|
||||
.getByLabelText("Please leave any additional information", {
|
||||
exact: false,
|
||||
})
|
||||
.props.onChange({ target: { value: "More info" } })
|
||||
);
|
||||
|
||||
act(() => {
|
||||
within(popover)
|
||||
.getByType("form")
|
||||
.props.onSubmit({});
|
||||
});
|
||||
|
||||
within(popover)
|
||||
.getByLabelText("Please leave any additional information", { exact: false })
|
||||
.props.onChange({ target: { value: "More info" } });
|
||||
|
||||
within(popover)
|
||||
.getByType("form")
|
||||
.props.onSubmit({});
|
||||
|
||||
await waitForElement(() =>
|
||||
within(popover).getByText("Thank you", { exact: false })
|
||||
);
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(popover).getByText("Thank you", { exact: false })
|
||||
);
|
||||
});
|
||||
expect(within(popover).toJSON()).toMatchSnapshot();
|
||||
|
||||
const reportedButton = within(comment).getByText("Reported", {
|
||||
@@ -185,7 +195,7 @@ it("dont agree with comment", async () => {
|
||||
within(testRenderer.root).getByTestID(`comment-${commentID}`)
|
||||
);
|
||||
const button = within(comment).getByText("Report", { selector: "button" });
|
||||
button.props.onClick();
|
||||
act(() => button.props.onClick());
|
||||
|
||||
const popover = within(testRenderer.root).getByID(
|
||||
button.props["aria-controls"]
|
||||
@@ -195,25 +205,37 @@ it("dont agree with comment", async () => {
|
||||
exact: false,
|
||||
});
|
||||
|
||||
radioButton.props.onChange({
|
||||
target: { type: "radio", value: radioButton.props.value },
|
||||
});
|
||||
|
||||
within(popover)
|
||||
.getByLabelText("Please leave any additional information", { exact: false })
|
||||
.props.onChange({ target: { value: "More info" } });
|
||||
|
||||
within(popover)
|
||||
.getByType("form")
|
||||
.props.onSubmit({});
|
||||
|
||||
await waitForElement(() =>
|
||||
within(popover).getByText("Thank you", { exact: false })
|
||||
act(() =>
|
||||
radioButton.props.onChange({
|
||||
target: { type: "radio", value: radioButton.props.value },
|
||||
})
|
||||
);
|
||||
|
||||
within(popover)
|
||||
.getByText("Dismiss", { exact: false })
|
||||
.props.onClick({});
|
||||
act(() =>
|
||||
within(popover)
|
||||
.getByLabelText("Please leave any additional information", {
|
||||
exact: false,
|
||||
})
|
||||
.props.onChange({ target: { value: "More info" } })
|
||||
);
|
||||
|
||||
act(() => {
|
||||
within(popover)
|
||||
.getByType("form")
|
||||
.props.onSubmit({});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(popover).getByText("Thank you", { exact: false })
|
||||
);
|
||||
});
|
||||
|
||||
act(() =>
|
||||
within(popover)
|
||||
.getByText("Dismiss", { exact: false })
|
||||
.props.onClick({})
|
||||
);
|
||||
|
||||
await wait(() =>
|
||||
expect(
|
||||
@@ -244,7 +266,7 @@ it("report comment as offensive and handle server error", async () => {
|
||||
within(testRenderer.root).getByTestID(`comment-${commentID}`)
|
||||
);
|
||||
const button = within(comment).getByText("Report", { selector: "button" });
|
||||
button.props.onClick();
|
||||
act(() => button.props.onClick());
|
||||
|
||||
const popover = within(testRenderer.root).getByID(
|
||||
button.props["aria-controls"]
|
||||
@@ -254,14 +276,20 @@ it("report comment as offensive and handle server error", async () => {
|
||||
"This comment is offensive"
|
||||
);
|
||||
|
||||
radioButton.props.onChange({
|
||||
target: { type: "radio", value: radioButton.props.value },
|
||||
act(() =>
|
||||
radioButton.props.onChange({
|
||||
target: { type: "radio", value: radioButton.props.value },
|
||||
})
|
||||
);
|
||||
|
||||
act(() => {
|
||||
within(popover)
|
||||
.getByType("form")
|
||||
.props.onSubmit({});
|
||||
});
|
||||
|
||||
within(popover)
|
||||
.getByType("form")
|
||||
.props.onSubmit({});
|
||||
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(popover).getByText("INTERNAL_ERROR"));
|
||||
await act(async () => {
|
||||
await waitForElement(() => within(popover).getByText("INTERNAL_ERROR"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { waitForElement, within } from "coral-framework/testHelpers";
|
||||
import { act, wait, waitForElement, within } from "coral-framework/testHelpers";
|
||||
|
||||
import { moderators, settings, stories } from "../fixtures";
|
||||
import create from "./create";
|
||||
@@ -32,14 +32,21 @@ async function createTestRenderer(
|
||||
},
|
||||
});
|
||||
|
||||
const tabPane = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("current-tab-pane")
|
||||
);
|
||||
return await act(async () => {
|
||||
const tabPane = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("current-tab-pane")
|
||||
);
|
||||
|
||||
return { testRenderer, tabPane };
|
||||
return { testRenderer, tabPane };
|
||||
});
|
||||
}
|
||||
|
||||
it("renders configure", async () => {
|
||||
const { tabPane } = await createTestRenderer();
|
||||
expect(within(tabPane).toJSON()).toMatchSnapshot();
|
||||
|
||||
await act(async () => {
|
||||
await wait(() => {
|
||||
expect(within(tabPane).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
MutationToUpdateStorySettingsResolver,
|
||||
} from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createMutationResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
@@ -50,13 +51,15 @@ const createTestRenderer = async (
|
||||
},
|
||||
});
|
||||
|
||||
const tabPane = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("current-tab-pane")
|
||||
);
|
||||
const applyButton = within(tabPane).getByText("Apply");
|
||||
const form = findParentWithType(applyButton, "form")!;
|
||||
return await act(async () => {
|
||||
const tabPane = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("current-tab-pane")
|
||||
);
|
||||
const applyButton = within(tabPane).getByText("Apply");
|
||||
const form = findParentWithType(applyButton, "form")!;
|
||||
|
||||
return { testRenderer, tabPane, applyButton, form };
|
||||
return { testRenderer, tabPane, applyButton, form };
|
||||
});
|
||||
};
|
||||
|
||||
it("change premod", async () => {
|
||||
@@ -80,18 +83,22 @@ it("change premod", async () => {
|
||||
|
||||
expect(applyButton.props.disabled).toBe(true);
|
||||
// Let's enable premod.
|
||||
premodField.props.onChange({});
|
||||
act(() => premodField.props.onChange({}));
|
||||
expect(applyButton.props.disabled).toBe(false);
|
||||
|
||||
// Send form
|
||||
form.props.onSubmit();
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
expect(applyButton.props.disabled).toBe(true);
|
||||
expect(premodField.props.disabled).toBe(true);
|
||||
|
||||
// Wait for submission to be finished
|
||||
await wait(() => {
|
||||
expect(premodField.props.disabled).toBe(false);
|
||||
await act(async () => {
|
||||
await wait(() => {
|
||||
expect(premodField.props.disabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
@@ -121,18 +128,22 @@ it("change premod links", async () => {
|
||||
|
||||
expect(applyButton.props.disabled).toBe(true);
|
||||
// Let's enable premod.
|
||||
premodLinksField.props.onChange({});
|
||||
act(() => premodLinksField.props.onChange({}));
|
||||
expect(applyButton.props.disabled).toBe(false);
|
||||
|
||||
// Send form
|
||||
form.props.onSubmit();
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
expect(applyButton.props.disabled).toBe(true);
|
||||
expect(premodLinksField.props.disabled).toBe(true);
|
||||
|
||||
// Wait for submission to be finished
|
||||
await wait(() => {
|
||||
expect(premodLinksField.props.disabled).toBe(false);
|
||||
await act(async () => {
|
||||
await wait(() => {
|
||||
expect(premodLinksField.props.disabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
@@ -166,28 +177,37 @@ it("change message box", async () => {
|
||||
|
||||
expect(applyButton.props.disabled).toBe(true);
|
||||
// Let's enable premod.
|
||||
enableField.props.onChange({});
|
||||
act(() => enableField.props.onChange({}));
|
||||
expect(applyButton.props.disabled).toBe(false);
|
||||
|
||||
// Select icon
|
||||
const iconButton = within(form).getByLabelText("question_answer");
|
||||
|
||||
iconButton.props.onChange({ target: { value: iconButton.props.value } });
|
||||
act(() =>
|
||||
iconButton.props.onChange({ target: { value: iconButton.props.value } })
|
||||
);
|
||||
|
||||
// Change content.
|
||||
(await waitForElement(() =>
|
||||
within(form).getByLabelText("Write a Message")
|
||||
)).props.onChange("*What do you think?*");
|
||||
const messageText = await act(async () => {
|
||||
return await waitForElement(() =>
|
||||
within(form).getByLabelText("Write a Message")
|
||||
);
|
||||
});
|
||||
act(() => messageText.props.onChange("*What do you think?*"));
|
||||
|
||||
// Send form
|
||||
form.props.onSubmit();
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
expect(applyButton.props.disabled).toBe(true);
|
||||
expect(enableField.props.disabled).toBe(true);
|
||||
|
||||
// Wait for submission to be finished
|
||||
await wait(() => {
|
||||
expect(enableField.props.disabled).toBe(false);
|
||||
await act(async () => {
|
||||
await wait(() => {
|
||||
expect(enableField.props.disabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
@@ -227,18 +247,24 @@ it("remove message icon", async () => {
|
||||
}),
|
||||
});
|
||||
|
||||
// Select icon
|
||||
const noIconButton = within(form).getByLabelText("No Icon");
|
||||
const noIconButton = await waitForElement(() =>
|
||||
within(form).getByLabelText("No Icon", { exact: false })
|
||||
);
|
||||
|
||||
noIconButton.props.onChange({ target: { value: noIconButton.props.value } });
|
||||
act(() =>
|
||||
noIconButton.props.onChange({ target: { value: noIconButton.props.value } })
|
||||
);
|
||||
|
||||
// Send form
|
||||
form.props.onSubmit();
|
||||
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
expect(applyButton.props.disabled).toBe(true);
|
||||
|
||||
// Wait for submission to be finished
|
||||
await wait(() => {
|
||||
expect(updateStorySettingsStub.called).toBe(true);
|
||||
await act(async () => {
|
||||
await wait(() => {
|
||||
expect(updateStorySettingsStub.called).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import {
|
||||
act,
|
||||
createSinonStub,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -98,13 +99,17 @@ it("loads more comments", async () => {
|
||||
/^historyComment-/
|
||||
).length;
|
||||
|
||||
within(commentHistory)
|
||||
.getByText("Load More")
|
||||
.props.onClick();
|
||||
act(() => {
|
||||
within(commentHistory)
|
||||
.getByText("Load More")
|
||||
.props.onClick();
|
||||
});
|
||||
|
||||
// Wait for loading.
|
||||
await wait(() =>
|
||||
expect(within(commentHistory).queryByText("Load More")).toBeNull()
|
||||
await act(() =>
|
||||
wait(() =>
|
||||
expect(within(commentHistory).queryByText("Load More")).toBeNull()
|
||||
)
|
||||
);
|
||||
|
||||
expect(within(commentHistory).getAllByTestID(/^historyComment-/).length).toBe(
|
||||
|
||||
Reference in New Issue
Block a user