[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:
Vinh
2019-12-03 01:07:50 +07:00
committed by GitHub
parent 4ee8cf7c96
commit bc27d7fbec
239 changed files with 4993 additions and 10263 deletions
@@ -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;
@@ -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,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 && (
@@ -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;
@@ -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());
});
});
@@ -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
}
}
`,
@@ -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>
@@ -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
}
@@ -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>
@@ -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";
@@ -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>
@@ -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";
@@ -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">
@@ -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>
@@ -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 &&
@@ -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(