[CORL-331] Better tests with types (#2270)

* feat: suspending, banning, now propogation

* feat: new mutation api with hooks support

* feat: better types in tests and refactor

* fix: lint
This commit is contained in:
Kiwi
2019-04-23 19:46:14 +00:00
committed by Wyatt Johnson
parent dbbc1af42e
commit 5150cdf60e
136 changed files with 2546 additions and 2465 deletions
@@ -22,42 +22,30 @@ const UserStatusChangeContainer: StatelessComponent<Props> = props => {
const banUser = useMutation(BanUserMutation);
const removeUserBan = useMutation(RemoveUserBanMutation);
const [showBanned, setShowBanned] = useState<boolean>(false);
const handleBan = useCallback(
() => {
if (props.user.status.ban.active) {
return;
}
setShowBanned(true);
},
[props.user, setShowBanned]
);
const handleRemoveBan = useCallback(
() => {
if (!props.user.status.ban.active) {
return;
}
removeUserBan({ userID: props.user.id });
},
[props.user, removeUserBan]
);
const handleSuspend = useCallback(
() => {
if (props.user.status.suspension.active) {
return;
}
// TODO: (cvle)
},
[props.user]
);
const handleRemoveSuspension = useCallback(
() => {
if (!props.user.status.suspension.active) {
return;
}
// TODO: (cvle)
},
[props.user]
);
const handleBan = useCallback(() => {
if (props.user.status.ban.active) {
return;
}
setShowBanned(true);
}, [props.user, setShowBanned]);
const handleRemoveBan = useCallback(() => {
if (!props.user.status.ban.active) {
return;
}
removeUserBan({ userID: props.user.id });
}, [props.user, removeUserBan]);
const handleSuspend = useCallback(() => {
if (props.user.status.suspension.active) {
return;
}
// TODO: (cvle)
}, [props.user]);
const handleRemoveSuspension = useCallback(() => {
if (!props.user.status.suspension.active) {
return;
}
// TODO: (cvle)
}, [props.user]);
if (props.user.role !== GQLUSER_ROLE.COMMENTER) {
return (
@@ -23,8 +23,8 @@ class NavigationWarningContainer extends React.Component<Props> {
"You have unsaved input. Are you sure you want to leave this page?"
);
this.removeTransitionHook = props.router.addTransitionHook(
() => (this.props.active ? warningMessage : true)
this.removeTransitionHook = props.router.addTransitionHook(() =>
this.props.active ? warningMessage : true
);
}
@@ -48,12 +48,11 @@ const CustomCSSConfig: StatelessComponent<Props> = ({ disabled }) => (
spellCheck={false}
fullWidth
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -49,12 +49,11 @@ const PermittedDomainsConfig: StatelessComponent<Props> = ({ disabled }) => (
spellCheck={false}
fullWidth
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -1,10 +1,10 @@
import { FormApi } from "final-form";
import { RouteProps } from "found";
import { merge } from "lodash";
import React from "react";
import { graphql } from "react-relay";
import { AdvancedConfigContainer_settings as SettingsData } from "talk-admin/__generated__/AdvancedConfigContainer_settings.graphql";
import { pureMerge } from "talk-common/utils";
import { withFragmentContainer } from "talk-framework/lib/relay";
import AdvancedConfig from "../components/AdvancedConfig";
@@ -28,7 +28,7 @@ class AdvancedConfigContainer extends React.Component<Props> {
}
private handleOnInitValues = (values: any) => {
this.initialValues = merge({}, this.initialValues, values);
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
@@ -36,12 +36,11 @@ const ClientSecretField: StatelessComponent<Props> = ({
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -41,12 +41,11 @@ const ClientSecretField: StatelessComponent<Props> = ({
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -55,7 +55,8 @@ const FacebookConfig: StatelessComponent<Props> = ({
>
<Typography>
To enable the integration with Facebook Authentication, you need to
create and set up a web application. For more information visit:<br />
create and set up a web application. For more information visit:
<br />
{"https://developers.facebook.com/docs/facebook-login/web"}
</Typography>
</Localized>
@@ -53,7 +53,8 @@ const GoogleConfig: StatelessComponent<Props> = ({ disabled, callbackURL }) => (
>
<Typography>
To enable the integration with Google Authentication you need to
create and set up a web application. For more information visit:<br />
create and set up a web application. For more information visit:
<br />
{
"https://developers.google.com/identity/protocols/OAuth2WebServer#creatingcred"
}
@@ -106,12 +106,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -168,12 +167,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
Discover
</Button>
</Flex>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -201,12 +199,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -234,12 +231,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -267,12 +263,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -1,11 +1,12 @@
import { FORM_ERROR, FormApi } from "final-form";
import { Localized } from "fluent-react/compat";
import { RouteProps } from "found";
import { get, merge } from "lodash";
import { get } from "lodash";
import React from "react";
import { graphql } from "react-relay";
import { AuthConfigContainer_auth as AuthData } from "talk-admin/__generated__/AuthConfigContainer_auth.graphql";
import { pureMerge } from "talk-common/utils";
import { TalkContext, withContext } from "talk-framework/lib/bootstrap";
import {
AddSubmitHook,
@@ -80,7 +81,7 @@ class AuthConfigContainer extends React.Component<Props> {
};
private handleOnInitValues = (values: any) => {
this.initialValues = merge({}, this.initialValues, values);
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
@@ -46,12 +46,11 @@ const ClosedStreamMessageConfig: StatelessComponent<Props> = ({ disabled }) => (
value={input.value}
/>
</Suspense>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -72,12 +72,11 @@ const ClosingCommentStreamsConfig: StatelessComponent<Props> = ({
value={input.value}
disabled={disabled}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -63,12 +63,11 @@ const CommentEditingConfig: StatelessComponent<Props> = ({ disabled }) => (
value={input.value}
disabled={disabled}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -97,12 +97,11 @@ const CommentLengthConfig: StatelessComponent<Props> = ({ disabled }) => (
textAlignCenter
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -148,12 +147,11 @@ const CommentLengthConfig: StatelessComponent<Props> = ({ disabled }) => (
textAlignCenter
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -68,12 +68,11 @@ const GuidelinesConfig: StatelessComponent<Props> = ({ disabled }) => (
value={input.value}
/>
</Suspense>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -81,12 +81,11 @@ const SitewideCommentingConfig: StatelessComponent<Props> = ({ disabled }) => (
value={input.value}
/>
</Suspense>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -1,10 +1,10 @@
import { FormApi } from "final-form";
import { RouteProps } from "found";
import { merge } from "lodash";
import React from "react";
import { graphql } from "react-relay";
import { GeneralConfigContainer_settings as SettingsData } from "talk-admin/__generated__/GeneralConfigContainer_settings.graphql";
import { pureMerge } from "talk-common/utils";
import { withFragmentContainer } from "talk-framework/lib/relay";
import GeneralConfig from "../components/GeneralConfig";
@@ -28,7 +28,7 @@ class GeneralConfigContainer extends React.Component<Props> {
}
private handleOnInitValues = (values: any) => {
this.initialValues = merge({}, this.initialValues, values);
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
@@ -39,12 +39,11 @@ const APIKeyField: StatelessComponent<Props> = ({
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -107,12 +107,11 @@ const AkismetConfig: StatelessComponent<Props> = ({ disabled }) => {
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -116,12 +116,11 @@ const PerspectiveConfig: StatelessComponent<Props> = ({ disabled }) => {
placeholder={TOXICITY_DEFAULT.toString()}
textAlignCenter
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -197,12 +196,11 @@ const PerspectiveConfig: StatelessComponent<Props> = ({ disabled }) => {
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -1,10 +1,10 @@
import { FormApi } from "final-form";
import { RouteProps } from "found";
import { merge } from "lodash";
import React from "react";
import { graphql } from "react-relay";
import { ModerationConfigContainer_settings as SettingsData } from "talk-admin/__generated__/ModerationConfigContainer_settings.graphql";
import { pureMerge } from "talk-common/utils";
import { withFragmentContainer } from "talk-framework/lib/relay";
import ModerationConfig from "../components/ModerationConfig";
@@ -28,7 +28,7 @@ class ModerationConfigContainer extends React.Component<Props> {
}
private handleOnInitValues = (values: any) => {
this.initialValues = merge({}, this.initialValues, values);
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
@@ -50,12 +50,11 @@ const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
spellCheck={false}
fullWidth
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -53,12 +53,11 @@ const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
spellCheck={false}
fullWidth
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -1,10 +1,10 @@
import { FormApi } from "final-form";
import { RouteProps } from "found";
import { merge } from "lodash";
import React from "react";
import { graphql } from "react-relay";
import { OrganizationConfigContainer_settings as SettingsData } from "talk-admin/__generated__/OrganizationConfigContainer_settings.graphql";
import { pureMerge } from "talk-common/utils";
import { withFragmentContainer } from "talk-framework/lib/relay";
import OrganizationConfig from "../components/OrganizationConfig";
@@ -28,7 +28,7 @@ class OrganizationConfigContainer extends React.Component<Props> {
}
private handleOnInitValues = (values: any) => {
this.initialValues = merge({}, this.initialValues, values);
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
@@ -47,12 +47,11 @@ const WordListTextArea: StatelessComponent<Props> = ({
autoCapitalize="off"
spellCheck={false}
/>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
@@ -1,10 +1,10 @@
import { FormApi } from "final-form";
import { RouteProps } from "found";
import { merge } from "lodash";
import React from "react";
import { graphql } from "react-relay";
import { WordListConfigContainer_settings as SettingsData } from "talk-admin/__generated__/WordListConfigContainer_settings.graphql";
import { pureMerge } from "talk-common/utils";
import { withFragmentContainer } from "talk-framework/lib/relay";
import WordListConfig from "../components/WordListConfig";
@@ -28,7 +28,7 @@ class WordListConfigContainer extends React.Component<Props> {
}
private handleOnInitValues = (values: any) => {
this.initialValues = merge({}, this.initialValues, values);
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
@@ -44,12 +44,11 @@ const EmailField: StatelessComponent<Props> = props => (
fullWidth
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
@@ -47,12 +47,11 @@ const ConfirmEmailField: StatelessComponent<Props> = props => (
fullWidth
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
@@ -44,12 +44,11 @@ const EmailField: StatelessComponent<Props> = props => (
fullWidth
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
@@ -53,12 +53,11 @@ const SetPasswordField: StatelessComponent<Props> = props => (
fullWidth
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
@@ -51,12 +51,11 @@ const CreateUsernameField: StatelessComponent<Props> = props => (
fullWidth
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
@@ -64,12 +64,11 @@ const SignInWithEmail: StatelessComponent<SignInWithEmailForm> = props => {
fullWidth
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
@@ -76,13 +76,12 @@ function markPhrasesHTML(
return text;
}
return tokens
.map(
(token, i) =>
// Using our Regexp patterns it returns tokens arranged this way
// [STRING_WITH_NO_MATCH, NEW_WORD_DELIMITER, MATCHED_WORD, ...].
// This pattern repeats throughout. Next line will mark MATCHED_WORD
// and escape all tokens.
i % 3 === 2 ? `<mark>${escapeHTML(token)}</mark>` : escapeHTML(token)
.map((token, i) =>
// Using our Regexp patterns it returns tokens arranged this way
// [STRING_WITH_NO_MATCH, NEW_WORD_DELIMITER, MATCHED_WORD, ...].
// This pattern repeats throughout. Next line will mark MATCHED_WORD
// and escape all tokens.
i % 3 === 2 ? `<mark>${escapeHTML(token)}</mark>` : escapeHTML(token)
)
.join("");
}
@@ -1,8 +1,9 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import { GQLResolver } from "talk-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
waitForElement,
@@ -12,39 +13,37 @@ import {
import create from "../create";
import { emptyModerationQueues, settings, users } from "../fixtures";
const viewer = users.admins[0];
async function createTestRenderer(
customResolver: any = {},
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
params: CreateTestRendererParams<GQLResolver> = {}
) {
replaceHistoryLocation("http://localhost/admin/login");
const resolvers = {
...customResolver,
Query: {
...customResolver.Query,
moderationQueues: sinon.stub().returns(emptyModerationQueues),
settings: sinon
.stub()
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
viewer: sinon
.stub()
.returns(
merge(
{ ...users.admins[0], email: "", username: "", profiles: [] },
get(customResolver, "Query.viewer")
)
),
},
};
const { testRenderer, context } = create({
// Set this to true, to see graphql responses.
logNetwork: options.logNetwork,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () =>
pureMerge<typeof viewer>(viewer, {
email: "",
username: "",
profiles: [],
}),
moderationQueues: () => emptyModerationQueues,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("SIGN_IN", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
@@ -62,44 +61,60 @@ it("renders addEmailAddress view", async () => {
it("renders createUsername view", async () => {
const { root } = await createTestRenderer({
Query: {
viewer: {
email: "hans@test.com",
resolvers: createResolversStub<GQLResolver>({
Query: {
viewer: () =>
pureMerge<typeof viewer>(viewer, {
email: "hans@test.com",
username: "",
profiles: [],
}),
},
},
}),
});
await waitForElement(() => within(root).queryByText("Create Username"));
});
it("renders createPassword view", async () => {
const { root } = await createTestRenderer({
Query: {
viewer: {
email: "hans@test.com",
username: "hans",
resolvers: createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
moderationQueues: () => emptyModerationQueues,
viewer: () =>
pureMerge<typeof viewer>(viewer, {
email: "hans@test.com",
username: "hans",
profiles: [],
}),
},
},
}),
});
await waitForElement(() => within(root).queryByText("Create Password"));
});
it("do not render createPassword view when local auth is disabled", async () => {
await createTestRenderer({
Query: {
viewer: {
email: "hans@test.com",
username: "hans",
},
settings: {
auth: {
integrations: {
local: {
enabled: false,
resolvers: createResolversStub<GQLResolver>({
Query: {
viewer: () =>
pureMerge<typeof viewer>(viewer, {
email: "hans@test.com",
username: "hans",
profiles: [],
}),
settings: () =>
pureMerge<typeof settings>(settings, {
auth: {
integrations: {
local: {
enabled: false,
},
},
},
},
},
}),
},
},
}),
});
await wait(() =>
@@ -111,13 +126,16 @@ it("do not render createPassword view when local auth is disabled", async () =>
it("complete account", async () => {
await createTestRenderer({
Query: {
viewer: {
email: "hans@test.com",
username: "hans",
profiles: [{ __typename: "LocalProfile" }],
resolvers: createResolversStub<GQLResolver>({
Query: {
viewer: () =>
pureMerge<typeof viewer>(viewer, {
email: "hans@test.com",
username: "hans",
profiles: [{ __typename: "LocalProfile" }],
}),
},
},
}),
});
await wait(() =>
expect(window.location.toString()).toBe(
@@ -1,8 +1,9 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import { GQLResolver } from "talk-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
toJSON,
wait,
@@ -13,33 +14,37 @@ import {
import create from "../create";
import { settings, users } from "../fixtures";
const viewer = users.admins[0];
async function createTestRenderer(
customResolver: any = {},
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
params: CreateTestRendererParams<GQLResolver> = {}
) {
replaceHistoryLocation("http://localhost/admin/login");
const resolvers = {
...customResolver,
Query: {
...customResolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
viewer: sinon.stub().returns({ ...users.admins[0], email: "" }),
},
};
const { testRenderer, context } = create({
// Set this to true, to see graphql responses.
logNetwork: options.logNetwork,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () =>
pureMerge<typeof viewer>(viewer, {
email: "",
}),
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("ADD_EMAIL_ADDRESS", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const container = await waitForElement(() =>
within(testRenderer.root).getByTestID("completeAccountBox")
);
@@ -94,21 +99,22 @@ it("accepts valid email", async () => {
it("shows server error", async () => {
const email = "hans@test.com";
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
throw new Error("server error");
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
setEmail: () => {
throw new Error("server error");
},
},
});
const {
form,
emailAddressField,
confirmEmailAddressField,
} = await createTestRenderer(
{
Mutation: {
setEmail,
},
},
{ muteNetworkErrors: true }
);
} = await createTestRenderer({
resolvers,
muteNetworkErrors: true,
});
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
@@ -130,27 +136,28 @@ it("shows server error", async () => {
it("successfully sets email", async () => {
const email = "hans@test.com";
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
expectAndFail(data.input).toEqual({
email,
clientMutationId: data.input.clientMutationId,
});
return {
user: {
id: "me",
email,
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
setEmail: ({ variables }) => {
expectAndFail(variables).toEqual({
email,
});
return {
user: {
id: "me",
email,
},
};
},
clientMutationId: data.input.clientMutationId,
};
},
});
const {
form,
emailAddressField,
confirmEmailAddressField,
} = await createTestRenderer({
Mutation: {
setEmail,
},
resolvers,
});
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
@@ -169,5 +176,5 @@ it("successfully sets email", async () => {
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(toJSON(form)).toMatchSnapshot();
expect(setEmail.called).toBe(true);
expect(resolvers.Mutation!.setEmail!.called).toBe(true);
});
@@ -1,8 +1,9 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import { GQLResolver } from "talk-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
toJSON,
wait,
@@ -13,33 +14,37 @@ import {
import create from "../create";
import { settings, users } from "../fixtures";
const viewer = users.admins[0];
async function createTestRenderer(
customResolver: any = {},
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
params: CreateTestRendererParams<GQLResolver> = {}
) {
replaceHistoryLocation("http://localhost/admin/login");
const resolvers = {
...customResolver,
Query: {
...customResolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
viewer: sinon.stub().returns({ ...users.admins[0], profiles: [] }),
},
};
const { testRenderer, context } = create({
// Set this to true, to see graphql responses.
logNetwork: options.logNetwork,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () =>
pureMerge<typeof viewer>(viewer, {
profiles: [],
}),
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("CREATE_PASSWORD", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const container = await waitForElement(() =>
within(testRenderer.root).getByTestID("completeAccountBox")
);
@@ -76,17 +81,18 @@ it("checks for invalid password", async () => {
it("shows server error", async () => {
const password = "secretpassword";
const setPassword = sinon.stub().callsFake((_: any, data: any) => {
throw new Error("server error");
});
const { form, passwordField } = await createTestRenderer(
{
Mutation: {
setPassword,
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
setPassword: () => {
throw new Error("server error");
},
},
{ muteNetworkErrors: true }
);
});
const { form, passwordField } = await createTestRenderer({
resolvers,
muteNetworkErrors: true,
});
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
@@ -104,23 +110,23 @@ it("shows server error", async () => {
it("successfully sets password", async () => {
const password = "secretpassword";
const setPassword = sinon.stub().callsFake((_: any, data: any) => {
expectAndFail(data.input).toEqual({
password,
clientMutationId: data.input.clientMutationId,
});
return {
user: {
id: "me",
profiles: [],
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
setPassword: ({ variables }) => {
expectAndFail(variables).toEqual({
password,
});
return {
user: {
id: "me",
profiles: [],
},
};
},
clientMutationId: data.input.clientMutationId,
};
},
});
const { form, passwordField } = await createTestRenderer({
Mutation: {
setPassword,
},
resolvers,
});
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
@@ -135,5 +141,5 @@ it("successfully sets password", async () => {
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(toJSON(form)).toMatchSnapshot();
expect(setPassword.called).toBe(true);
expect(resolvers.Mutation!.setPassword!.called).toBe(true);
});
@@ -1,8 +1,9 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import { GQLResolver } from "talk-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
toJSON,
wait,
@@ -14,29 +15,27 @@ import create from "../create";
import { settings } from "../fixtures";
async function createTestRenderer(
customResolver: any = {},
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
params: CreateTestRendererParams<GQLResolver> = {}
) {
replaceHistoryLocation("http://localhost/admin/login");
const resolvers = {
...customResolver,
Query: {
...customResolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
},
};
const { testRenderer, context } = create({
// Set this to true, to see graphql responses.
logNetwork: options.logNetwork,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("CREATE_USERNAME", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const container = await waitForElement(() =>
@@ -75,17 +74,17 @@ it("checks for invalid username", async () => {
it("shows server error", async () => {
const username = "hans";
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
throw new Error("server error");
});
const { form, usernameField } = await createTestRenderer(
{
Mutation: {
setUsername,
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
setUsername: () => {
throw new Error("server error");
},
},
{ muteNetworkErrors: true }
);
});
const { form, usernameField } = await createTestRenderer({
resolvers,
muteNetworkErrors: true,
});
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
@@ -103,24 +102,25 @@ it("shows server error", async () => {
it("successfully sets username", async () => {
const username = "hans";
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
expectAndFail(data.input).toEqual({
username,
clientMutationId: data.input.clientMutationId,
});
return {
user: {
id: "me",
username,
},
clientMutationId: data.input.clientMutationId,
};
});
const { form, usernameField } = await createTestRenderer({
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
setUsername,
setUsername: ({ variables }) => {
expectAndFail(variables).toEqual({
username,
});
return {
user: {
id: "me",
username,
},
};
},
},
});
const { form, usernameField } = await createTestRenderer({
resolvers,
});
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
@@ -134,5 +134,5 @@ it("successfully sets username", async () => {
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(toJSON(form)).toMatchSnapshot();
expect(setUsername.called).toBe(true);
expect(resolvers.Mutation!.setUsername!.called).toBe(true);
});
@@ -1,10 +1,17 @@
import sinon from "sinon";
import {
GQLResolver,
QueryToModerationQueuesResolver,
} from "talk-framework/schema";
import {
createAccessToken,
createQueryResolverStub,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
} from "talk-framework/testHelpers";
import { pureMerge } from "talk-common/utils";
import create from "../create";
import {
emptyModerationQueues,
@@ -13,26 +20,41 @@ import {
users,
} from "../fixtures";
const resolvers = {
Query: {
settings: sinon.stub().returns(settings),
moderationQueues: sinon.stub().returns(emptyModerationQueues),
comments: sinon.stub().returns(emptyRejectedComments),
viewer: sinon.stub().returns(users.admins[0]),
},
};
const viewer = users.admins[0];
it("redirect when already logged in", async () => {
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
replaceHistoryLocation("http://localhost/admin/login");
create({
resolvers,
// Set this to true, to see graphql responses.
logNetwork: false,
initLocalState: localRecord => {
const { testRenderer, context } = create({
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
moderationQueues: createQueryResolverStub<
QueryToModerationQueuesResolver
>(() => emptyModerationQueues),
comments: () => emptyRejectedComments,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
localRecord.setValue("SIGN_IN", "authView");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
return { testRenderer, context };
}
it("redirect when already logged in", async () => {
await createTestRenderer();
await wait(() =>
expect(window.location.toString()).toBe(
"http://localhost/admin/moderate/reported"
@@ -41,14 +63,8 @@ it("redirect when already logged in", async () => {
});
it("redirect to redirectPath when already logged in", async () => {
replaceHistoryLocation("http://localhost/admin/login");
create({
resolvers,
// Set this to true, to see graphql responses.
logNetwork: false,
await createTestRenderer({
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
localRecord.setValue("/admin/moderate/pending", "redirectPath");
},
});
@@ -1,9 +1,16 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { replaceHistoryLocation, wait } from "talk-framework/testHelpers";
import { pureMerge } from "talk-common/utils";
import { LOCAL_ID, lookup } from "talk-framework/lib/relay";
import {
GQLResolver,
QueryToModerationQueuesResolver,
} from "talk-framework/schema";
import {
createQueryResolverStub,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
} from "talk-framework/testHelpers";
import create from "../create";
import {
@@ -12,39 +19,41 @@ import {
settings,
} from "../fixtures";
function createTestRenderer(): {
testRenderer: ReactTestRenderer;
context: TalkContext;
} {
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
replaceHistoryLocation("http://localhost/admin/moderate");
const resolvers = {
Query: {
settings: sinon.stub().returns(settings),
moderationQueues: sinon.stub().returns(emptyModerationQueues),
comments: sinon.stub().returns(emptyRejectedComments),
},
};
const { testRenderer, context } = create({
resolvers,
// Set this to true, to see graphql responses.
logNetwork: false,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
moderationQueues: createQueryResolverStub<
QueryToModerationQueuesResolver
>(() => emptyModerationQueues),
comments: () => emptyRejectedComments,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(false, "loggedIn");
localRecord.setValue("SIGN_IN", "authView");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
return { testRenderer, context };
}
it("redirect when not logged in", async () => {
const { context } = createTestRenderer();
const { context } = await createTestRenderer();
await wait(() => {
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.redirectPath
).toBe("/admin/moderate/reported");
expect(lookup(context.relayEnvironment, LOCAL_ID)!.redirectPath).toBe(
"/admin/moderate/reported"
);
expect(window.location.toString()).toBe("http://localhost/admin/login");
});
});
@@ -1,11 +1,12 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { GQLUSER_ROLE } from "talk-framework/schema";
import { pureMerge } from "talk-common/utils";
import { LOCAL_ID, lookup } from "talk-framework/lib/relay";
import { GQLResolver, GQLUSER_ROLE } from "talk-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
waitForElement,
@@ -20,29 +21,32 @@ import {
users,
} from "../fixtures";
function createTestRenderer(
userDiff: any = {}
): {
testRenderer: ReactTestRenderer;
context: TalkContext;
} {
const viewer = users.admins[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
replaceHistoryLocation("http://localhost/admin/moderate/reported");
const resolvers = {
Query: {
settings: sinon.stub().returns(settings),
moderationQueues: sinon.stub().returns(emptyModerationQueues),
comments: sinon.stub().returns(emptyRejectedComments),
viewer: sinon.stub().returns({ ...users.admins[0], ...userDiff }),
},
};
const { testRenderer, context } = create({
resolvers,
// Set this to true, to see graphql responses.
logNetwork: false,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
moderationQueues: () => emptyModerationQueues,
comments: () => emptyRejectedComments,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
localRecord.setValue("SIGN_IN", "authView");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
return { testRenderer, context };
@@ -51,7 +55,16 @@ function createTestRenderer(
it("show restricted screen for commenters and staff", async () => {
const restrictedRoles = [GQLUSER_ROLE.COMMENTER, GQLUSER_ROLE.STAFF];
for (const role of restrictedRoles) {
const { testRenderer } = createTestRenderer({ role });
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
viewer: () =>
pureMerge<typeof viewer>(viewer, {
role,
}),
},
}),
});
const authBox = await waitForElement(() =>
within(testRenderer.root).getByTestID("authBox")
);
@@ -60,8 +73,15 @@ it("show restricted screen for commenters and staff", async () => {
});
it("sign out when clicking on sign in as", async () => {
const { context, testRenderer } = createTestRenderer({
role: GQLUSER_ROLE.COMMENTER,
const { testRenderer, context } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
viewer: () =>
pureMerge<typeof viewer>(viewer, {
role: GQLUSER_ROLE.COMMENTER,
}),
},
}),
});
const authBox = await waitForElement(() =>
within(testRenderer.root).getByTestID("authBox")
@@ -81,18 +101,10 @@ it("sign out when clicking on sign in as", async () => {
.props.onClick();
await wait(() => {
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.redirectPath
).toBe("/admin/moderate/reported");
expect(lookup(context.relayEnvironment, LOCAL_ID)!.redirectPath).toBe(
"/admin/moderate/reported"
);
});
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.loggedIn
).toBeFalsy();
expect(lookup(context.relayEnvironment, LOCAL_ID)!.loggedIn).toBeFalsy();
});
@@ -1,8 +1,11 @@
import { ReactTestInstance } from "react-test-renderer";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import { GQLResolver } from "talk-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
waitForElement,
@@ -16,32 +19,34 @@ import {
settings,
} from "../fixtures";
const resolvers = {
Query: {
settings: sinon.stub().returns(settings),
moderationQueues: sinon.stub().returns(emptyModerationQueues),
comments: sinon.stub().returns(emptyRejectedComments),
},
};
const inputPredicate = (name: string) => (n: ReactTestInstance) => {
return n.props.name === name && n.props.onChange;
};
async function createTestRenderer() {
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
// deliberately setting to a different route,
// it should be smart enough to reroute to /admin/login.
replaceHistoryLocation("http://localhost/admin/moderate");
const { testRenderer, context } = create({
resolvers,
// Set this to true, to see graphql responses.
logNetwork: false,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
moderationQueues: () => emptyModerationQueues,
comments: () => emptyRejectedComments,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(false, "loggedIn");
localRecord.setValue("SIGN_IN", "authView");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const form = await waitForElement(() =>
within(testRenderer.root).getByType("form")
);
@@ -121,11 +126,11 @@ it("shows server error", async () => {
it("submits form successfully", async () => {
const { form, context } = await createTestRenderer();
form
.find(inputPredicate("email"))
within(form)
.getByLabelText("Email Address")
.props.onChange({ target: { value: "hans@test.com" } });
form
.find(inputPredicate("password"))
within(form)
.getByLabelText("Password")
.props.onChange({ target: { value: "testtest" } });
const accessToken = createAccessToken();
@@ -1,7 +1,11 @@
import sinon from "sinon";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { pureMerge } from "talk-common/utils";
import { LOCAL_ID, lookup } from "talk-framework/lib/relay";
import { GQLResolver } from "talk-framework/schema";
import {
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
waitForElement,
@@ -16,26 +20,39 @@ import {
users,
} from "../fixtures";
const resolvers = {
Query: {
settings: sinon.stub().returns(settings),
moderationQueues: sinon.stub().returns(emptyModerationQueues),
comments: sinon.stub().returns(emptyRejectedComments),
viewer: sinon.stub().returns(users.admins[0]),
},
};
const viewer = users.admins[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
replaceHistoryLocation("http://localhost/admin/moderate/reported");
const { testRenderer, context } = create({
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
moderationQueues: () => emptyModerationQueues,
comments: () => emptyRejectedComments,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
return { testRenderer, context };
}
it("logs out", async () => {
replaceHistoryLocation("http://localhost/admin/moderate");
const { testRenderer, context } = create({
resolvers,
// Set this to true, to see graphql responses.
logNetwork: false,
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
},
});
const { testRenderer, context } = await createTestRenderer();
const restMock = sinon.mock(context.rest);
restMock
@@ -60,11 +77,6 @@ it("logs out", async () => {
signOutButton.props.onClick();
await wait(() => {
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.loggedIn
).toBeFalsy();
expect(lookup(context.relayEnvironment, LOCAL_ID)!.loggedIn).toBeFalsy();
});
});
@@ -1,16 +1,16 @@
import { merge } from "lodash";
import TestRenderer from "react-test-renderer";
import { pureMerge } from "talk-common/utils";
import {
GQLResolver,
GQLUSER_ROLE,
GQLUSER_STATUS,
QueryToSettingsResolver,
QueryToUsersResolver,
QueryToViewerResolver,
} from "talk-framework/schema";
import {
createMutationResolverStub,
createQueryResolverStub,
createResolversStub,
CreateTestRendererParams,
findParentWithType,
replaceHistoryLocation,
waitForElement,
@@ -18,11 +18,6 @@ import {
within,
} from "talk-framework/testHelpers";
import {
BanUserMutation,
RemoveUserBanMutation,
UpdateUserRoleMutation,
} from "talk-admin/mutations";
import create from "../create";
import {
communityUsers,
@@ -35,29 +30,29 @@ beforeEach(async () => {
replaceHistoryLocation("http://localhost/admin/community");
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
settings: createQueryResolverStub<QueryToSettingsResolver>(
() => settings
),
users: createQueryResolverStub<QueryToUsersResolver>(variables => {
expectAndFail(variables.role).toBeFalsy();
return communityUsers;
}),
viewer: createQueryResolverStub<QueryToViewerResolver>(
() => users.admins[0]
),
...resolver.Query,
},
};
const createTestRenderer = async (
params: CreateTestRendererParams<GQLResolver> = {}
) => {
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
users: ({ variables }) => {
expectAndFail(variables.role).toBeFalsy();
return communityUsers;
},
viewer: () => users.admins[0],
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const container = await waitForElement(() =>
@@ -73,10 +68,12 @@ it("renders community", async () => {
it("renders empty community", async () => {
const { container } = await createTestRenderer({
Query: {
users: createQueryResolverStub<QueryToUsersResolver>(
() => emptyCommunityUsers
),
resolvers: {
Query: {
users: createQueryResolverStub<QueryToUsersResolver>(
() => emptyCommunityUsers
),
},
},
});
expect(within(container).toJSON()).toMatchSnapshot();
@@ -84,9 +81,9 @@ it("renders empty community", async () => {
it("filter by role", async () => {
const { container } = await createTestRenderer({
Query: {
users: createQueryResolverStub<QueryToUsersResolver>(
(variables, callCount) => {
resolvers: createResolversStub<GQLResolver>({
Query: {
users: ({ variables, callCount }) => {
switch (callCount) {
case 0:
return communityUsers;
@@ -94,9 +91,9 @@ it("filter by role", async () => {
expectAndFail(variables.role).toBe(GQLUSER_ROLE.COMMENTER);
return emptyCommunityUsers;
}
}
),
},
},
},
}),
});
const selectField = within(container).getByLabelText("Search by role");
@@ -127,21 +124,24 @@ it("can't change viewer role", async () => {
it("change user role", async () => {
const user = users.commenters[0];
const updateUserRole = createMutationResolverStub<
typeof UpdateUserRoleMutation
>(variables => {
expectAndFail(variables).toMatchObject({
userID: user.id,
role: GQLUSER_ROLE.STAFF,
});
const userRecord = merge({}, user, { role: variables.role });
return {
user: userRecord,
};
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateUserRole: ({ variables }) => {
expectAndFail(variables).toMatchObject({
userID: user.id,
role: GQLUSER_ROLE.STAFF,
});
const userRecord = pureMerge<typeof user>(user, {
role: variables.role,
});
return {
user: userRecord,
};
},
},
});
const { container } = await createTestRenderer({
Mutation: { updateUserRole },
resolvers,
});
const userRow = within(container).getByText(user.username!, {
@@ -165,29 +165,34 @@ it("change user role", async () => {
});
within(userRow).getByText("Staff");
expect(updateUserRole.called).toBe(true);
expect(resolvers.Mutation!.updateUserRole!.called).toBe(true);
});
it("can't change role as a moderator", async () => {
const viewer = users.moderators[0];
const { container } = await createTestRenderer({
Query: {
viewer: createQueryResolverStub<QueryToViewerResolver>(() => viewer),
},
resolvers: createResolversStub<GQLResolver>({
Query: {
viewer: () => viewer,
},
}),
});
expect(() => within(container).getByLabelText("Change role")).toThrow();
});
it("load more", async () => {
const { container } = await createTestRenderer({
Query: {
users: createQueryResolverStub<QueryToUsersResolver>(
(variables, callCount) => {
resolvers: createResolversStub<GQLResolver>({
Query: {
users: ({ callCount }) => {
switch (callCount) {
case 0:
return {
edges: [
{ node: users.admins[0], cursor: users.admins[0].createdAt },
{
node: users.admins[0],
cursor: users.admins[0].createdAt,
},
{
node: users.commenters[0],
cursor: users.commenters[0].createdAt,
@@ -212,9 +217,9 @@ it("load more", async () => {
},
};
}
}
),
},
},
},
}),
});
const loadMore = within(container).getByText("Load More");
TestRenderer.act(() => {
@@ -230,9 +235,9 @@ it("load more", async () => {
it("filter by search", async () => {
const { container } = await createTestRenderer({
Query: {
users: createQueryResolverStub<QueryToUsersResolver>(
(variables, callCount) => {
resolvers: createResolversStub<GQLResolver>({
Query: {
users: ({ variables, callCount }) => {
switch (callCount) {
case 0:
return communityUsers;
@@ -240,9 +245,9 @@ it("filter by search", async () => {
expectAndFail(variables.query).toBe("search");
return emptyCommunityUsers;
}
}
),
},
},
},
}),
});
const searchField = within(container).getByLabelText("Search by username", {
@@ -264,9 +269,9 @@ it("filter by search", async () => {
it("filter by status", async () => {
const { container } = await createTestRenderer({
Query: {
users: createQueryResolverStub<QueryToUsersResolver>(
(variables, callCount) => {
resolvers: createResolversStub<GQLResolver>({
Query: {
users: ({ variables, callCount }) => {
switch (callCount) {
case 0:
return communityUsers;
@@ -274,9 +279,9 @@ it("filter by status", async () => {
expectAndFail(variables.status).toBe("BANNED");
return emptyCommunityUsers;
}
}
),
},
},
},
}),
});
const statusField = within(container).getByLabelText(
@@ -314,25 +319,28 @@ it("can't change staff, moderator and admin status", async () => {
it("ban user", async () => {
const user = users.commenters[0];
const banUser = createMutationResolverStub<typeof BanUserMutation>(
variables => {
expectAndFail(variables).toMatchObject({
userID: user.id,
});
const userRecord = merge({}, user, {
status: {
current: user.status.current.concat(GQLUSER_STATUS.BANNED),
banned: { active: true },
},
});
return {
user: userRecord,
};
}
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
banUser: ({ variables }) => {
expectAndFail(variables).toMatchObject({
userID: user.id,
});
const userRecord = pureMerge<typeof user>(user, {
status: {
current: user.status.current.concat(GQLUSER_STATUS.BANNED),
ban: { active: true },
},
});
return {
user: userRecord,
};
},
},
});
const { container, testRenderer } = await createTestRenderer({
Mutation: { banUser },
resolvers,
});
const userRow = within(container).getByText(user.username!, {
@@ -368,32 +376,32 @@ it("ban user", async () => {
.getByText("Ban User")
.props.onClick();
within(userRow).getByText("Banned");
expect(banUser.called).toBe(true);
expect(resolvers.Mutation!.banUser!.called).toBe(true);
});
it("remove user ban", async () => {
const user = users.bannedCommenter;
const removeUserBan = createMutationResolverStub<
typeof RemoveUserBanMutation
>(variables => {
expectAndFail(variables).toMatchObject({
userID: user.id,
});
const userRecord = merge({}, user, {
status: {
current: user.status.current.filter(s => s !== GQLUSER_STATUS.BANNED),
banned: { active: false },
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
removeUserBan: ({ variables }) => {
expectAndFail(variables).toMatchObject({
userID: user.id,
});
const userRecord = pureMerge<typeof user>(user, {
status: {
current: user.status.current.filter(
s => s !== GQLUSER_STATUS.BANNED
),
ban: { active: false },
},
});
return {
user: userRecord,
};
},
});
return {
user: userRecord,
};
});
const { container } = await createTestRenderer({
Mutation: { removeUserBan },
},
Query: {
users: createQueryResolverStub<QueryToUsersResolver>(() => ({
users: () => ({
edges: [
{
node: user,
@@ -401,10 +409,14 @@ it("remove user ban", async () => {
},
],
pageInfo: { endCursor: null, hasNextPage: false },
})),
}),
},
});
const { container } = await createTestRenderer({
resolvers,
});
const userRow = within(container).getByText(user.username!, {
selector: "tr",
});
@@ -426,5 +438,5 @@ it("remove user ban", async () => {
});
within(userRow).getByText("Active");
expect(removeUserBan.called).toBe(true);
expect(resolvers.Mutation!.removeUserBan!.called).toBe(true);
});
@@ -1,8 +1,8 @@
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import { GQLResolver } from "talk-framework/schema";
import {
createSinonStub,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
waitForElement,
@@ -16,25 +16,30 @@ beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/advanced");
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
viewer: sinon.stub().returns(users.admins[0]),
},
};
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
const viewer = users.admins[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer, context } = create({
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const configureContainer = await waitForElement(() =>
within(testRenderer.root).getByTestID("configure-container")
);
@@ -45,12 +50,13 @@ const createTestRenderer = async (resolver: any = {}) => {
"configure-sideBar-saveChanges"
);
return {
context,
testRenderer,
configureContainer,
advancedContainer,
saveChangesButton,
};
};
}
it("renders configure advanced", async () => {
const { configureContainer } = await createTestRenderer();
@@ -58,25 +64,22 @@ it("renders configure advanced", async () => {
});
it("change custom css", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.customCSSURL).toEqual("./custom.css");
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.customCSSURL).toEqual("./custom.css");
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
advancedContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
resolvers,
});
const customCSSField = within(advancedContainer).getByLabelText("Custom CSS");
@@ -99,29 +102,26 @@ it("change custom css", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
it("change permitted domains to be empty", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.domains).toEqual([]);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.domains).toEqual([]);
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
advancedContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
resolvers,
});
const permittedDomainsField = within(advancedContainer).getByLabelText(
@@ -146,32 +146,29 @@ it("change permitted domains to be empty", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
it("change permitted domains to include more domains", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.domains).toEqual([
"localhost:8080",
"localhost:3000",
]);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.domains).toEqual([
"localhost:8080",
"localhost:3000",
]);
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
advancedContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
resolvers,
});
const permittedDomainsField = within(advancedContainer).getByLabelText(
@@ -196,5 +193,5 @@ it("change permitted domains to include more domains", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
+146 -128
View File
@@ -1,10 +1,13 @@
import { cloneDeep, get, merge } from "lodash";
import { cloneDeep } from "lodash";
import { ReactTestInstance } from "react-test-renderer";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { pureMerge } from "talk-common/utils";
import { GQLResolver } from "talk-framework/schema";
import {
createSinonStub,
inputPredicate,
createResolversStub,
CreateTestRendererParams,
limitSnapshotTo,
replaceHistoryLocation,
waitForElement,
@@ -14,29 +17,44 @@ import {
import create from "../create";
import { settingsWithEmptyAuth, users } from "../fixtures";
/**
* This is depreacted, do not use it anymore.
* @deprecated
*/
const deprecatedInputPredicate = (nameOrID: string) => (
n: ReactTestInstance
) => {
return (
[n.props.name, n.props.id].indexOf(nameOrID) > -1 &&
["input", "button"].indexOf(n.type as string) > -1
);
};
beforeEach(async () => {
replaceHistoryLocation("http://localhost/admin/configure/auth");
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(
merge({}, settingsWithEmptyAuth, get(resolver, "Query.settings"))
),
viewer: sinon.stub().returns(users.admins[0]),
},
};
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
const viewer = users.admins[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer, context } = create({
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settingsWithEmptyAuth,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const configureContainer = await waitForElement(() =>
@@ -45,8 +63,8 @@ const createTestRenderer = async (resolver: any = {}) => {
const authContainer = await waitForElement(() =>
within(configureContainer).getByTestID("configure-authContainer")
);
return { testRenderer, configureContainer, authContainer };
};
return { context, testRenderer, configureContainer, authContainer };
}
it("renders configure auth", async () => {
const { configureContainer } = await createTestRenderer();
@@ -55,32 +73,34 @@ it("renders configure auth", async () => {
it("regenerate sso key", async () => {
const { testRenderer } = await createTestRenderer({
Mutation: {
regenerateSSOKey: createSinonStub(s =>
s.callsFake((_: any, data: any) => {
resolvers: createResolversStub<GQLResolver>({
Mutation: {
regenerateSSOKey: () => {
return {
settings: merge({}, settingsWithEmptyAuth, {
auth: {
integrations: {
sso: {
key: "==GENERATED_KEY==",
keyGeneratedAt: "2018-11-12T23:26:06.239Z",
settings: pureMerge<typeof settingsWithEmptyAuth>(
settingsWithEmptyAuth,
{
auth: {
integrations: {
sso: {
key: "==GENERATED_KEY==",
keyGeneratedAt: "2018-11-12T23:26:06.239Z",
},
},
},
},
}),
clientMutationId: data.input.clientMutationId,
}
),
};
})
),
},
},
},
}),
});
testRenderer.root
.find(inputPredicate("auth.integrations.sso.enabled"))
.find(deprecatedInputPredicate("auth.integrations.sso.enabled"))
.props.onChange({});
testRenderer.root
.find(inputPredicate("configure-auth-sso-regenerate"))
.find(deprecatedInputPredicate("configure-auth-sso-regenerate"))
.props.onClick();
await timeout();
@@ -95,7 +115,7 @@ it("prevents admin lock out", async () => {
// Let's disable local auth.
testRenderer.root
.find(inputPredicate("auth.integrations.local.enabled"))
.find(deprecatedInputPredicate("auth.integrations.local.enabled"))
.props.onChange();
// Send form
@@ -109,10 +129,10 @@ it("prevents admin lock out", async () => {
it("prevents stream lock out", async () => {
let settingsRecord = cloneDeep(settingsWithEmptyAuth);
const { testRenderer } = await createTestRenderer({
Mutation: {
updateSettings: createSinonStub(s =>
s.callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.auth.integrations.local).toEqual({
resolvers: createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.auth!.integrations!.local).toEqual({
enabled: true,
allowRegistration: true,
targetFilter: {
@@ -120,14 +140,13 @@ it("prevents stream lock out", async () => {
stream: false,
},
});
settingsRecord = merge({}, settingsRecord, data.input.settings);
settingsRecord = pureMerge(settingsRecord, variables.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
),
},
},
},
}),
});
const origConfirm = window.confirm;
const stubContinue = sinon.stub().returns(true);
@@ -137,7 +156,9 @@ it("prevents stream lock out", async () => {
window.confirm = stubCancel;
// Let's disable stream target in local auth.
testRenderer.root
.find(inputPredicate("auth.integrations.local.targetFilter.stream"))
.find(
deprecatedInputPredicate("auth.integrations.local.targetFilter.stream")
)
.props.onChange();
// Send form
@@ -154,7 +175,9 @@ it("prevents stream lock out", async () => {
window.confirm = stubContinue;
// Let's enable stream target in local auth.
testRenderer.root
.find(inputPredicate("auth.integrations.local.targetFilter.stream"))
.find(
deprecatedInputPredicate("auth.integrations.local.targetFilter.stream")
)
.props.onChange();
// Send form
@@ -167,83 +190,74 @@ it("prevents stream lock out", async () => {
});
it("change settings", async () => {
let settingsRecord = cloneDeep(settingsWithEmptyAuth);
const { testRenderer } = await createTestRenderer({
Query: {
discoverOIDCConfiguration: createSinonStub(s =>
s.callsFake((_: any, data: any) => {
expectAndFail(data).toEqual({ issuer: "http://issuer.com" });
resolvers: createResolversStub<GQLResolver>({
Query: {
discoverOIDCConfiguration: ({ variables }) => {
expectAndFail(variables).toEqual({ issuer: "http://issuer.com" });
return {
issuer: "http://issuer.com",
tokenURL: "http://issuer.com/tokenURL",
jwksURI: "http://issuer.com/jwksURI",
authorizationURL: "http://issuer.com/authorizationURL",
};
})
),
},
Mutation: {
updateSettings: createSinonStub(
s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(
data.input.settings.auth.integrations.facebook
).toEqual({
enabled: true,
allowRegistration: true,
targetFilter: {
admin: true,
stream: true,
},
clientID: "myClientID",
clientSecret: "myClientSecret",
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
}),
s =>
s.onSecondCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.auth.integrations.oidc).toEqual({
enabled: true,
allowRegistration: false,
targetFilter: {
admin: true,
stream: true,
},
name: "name",
clientID: "clientID",
clientSecret: "clientSecret",
issuer: "http://issuer.com",
jwksURI: "http://issuer.com/jwksURI",
authorizationURL: "http://issuer.com/authorizationURL",
tokenURL: "http://issuer.com/tokenURL",
});
(settingsRecord.auth.integrations.oidc as any) = merge(
settingsRecord.auth.integrations.oidc,
data.input.configuration
);
return {
integration: settingsRecord.auth.integrations.oidc,
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
),
},
},
},
Mutation: {
updateSettings: ({ variables, callCount }) => {
switch (callCount) {
case 0:
expectAndFail(
variables.settings.auth!.integrations!.facebook
).toEqual({
enabled: true,
allowRegistration: true,
targetFilter: {
admin: true,
stream: true,
},
clientID: "myClientID",
clientSecret: "myClientSecret",
});
return {
settings: pureMerge(settingsWithEmptyAuth, variables.settings),
};
default:
expectAndFail(
variables.settings.auth!.integrations!.oidc
).toEqual({
enabled: true,
allowRegistration: false,
targetFilter: {
admin: true,
stream: true,
},
name: "name",
clientID: "clientID",
clientSecret: "clientSecret",
issuer: "http://issuer.com",
jwksURI: "http://issuer.com/jwksURI",
authorizationURL: "http://issuer.com/authorizationURL",
tokenURL: "http://issuer.com/tokenURL",
});
return {
settings: pureMerge(settingsWithEmptyAuth, variables.settings),
};
}
},
},
}),
});
// Let's change some facebook settings.
testRenderer.root
.find(inputPredicate("auth.integrations.facebook.enabled"))
.find(deprecatedInputPredicate("auth.integrations.facebook.enabled"))
.props.onChange({});
testRenderer.root
.find(inputPredicate("auth.integrations.facebook.clientID"))
.find(deprecatedInputPredicate("auth.integrations.facebook.clientID"))
.props.onChange("myClientID");
testRenderer.root
.find(inputPredicate("auth.integrations.facebook.clientSecret"))
.find(deprecatedInputPredicate("auth.integrations.facebook.clientSecret"))
.props.onChange("myClientSecret");
expect(
limitSnapshotTo("configure-auth-facebook-container", testRenderer.toJSON())
@@ -262,18 +276,20 @@ it("change settings", async () => {
// Disable other fields while submitting
// We are only testing for one here right now..
expect(
testRenderer.root.find(inputPredicate("auth.integrations.facebook.enabled"))
.props.disabled
testRenderer.root.find(
deprecatedInputPredicate("auth.integrations.facebook.enabled")
).props.disabled
).toBe(true);
await timeout();
expect(
testRenderer.root.find(inputPredicate("auth.integrations.facebook.enabled"))
.props.disabled
testRenderer.root.find(
deprecatedInputPredicate("auth.integrations.facebook.enabled")
).props.disabled
).toBe(false);
// Now let's enable oidc
testRenderer.root
.find(inputPredicate("auth.integrations.oidc.enabled"))
.find(deprecatedInputPredicate("auth.integrations.oidc.enabled"))
.props.onChange({});
expect(
@@ -288,21 +304,21 @@ it("change settings", async () => {
// Fill form
testRenderer.root
.find(inputPredicate("auth.integrations.oidc.name"))
.find(deprecatedInputPredicate("auth.integrations.oidc.name"))
.props.onChange("name");
testRenderer.root
.find(inputPredicate("auth.integrations.oidc.clientID"))
.find(deprecatedInputPredicate("auth.integrations.oidc.clientID"))
.props.onChange("clientID");
testRenderer.root
.find(inputPredicate("auth.integrations.oidc.clientSecret"))
.find(deprecatedInputPredicate("auth.integrations.oidc.clientSecret"))
.props.onChange("clientSecret");
testRenderer.root
.find(inputPredicate("auth.integrations.oidc.issuer"))
.find(deprecatedInputPredicate("auth.integrations.oidc.issuer"))
.props.onChange("http://issuer.com");
// Discover the rest.
testRenderer.root
.find(inputPredicate("configure-auth-oidc-discover"))
.find(deprecatedInputPredicate("configure-auth-oidc-discover"))
.props.onClick();
await timeout();
@@ -315,12 +331,14 @@ it("change settings", async () => {
// Disable other fields while submitting
// We are only testing for one here right now..
expect(
testRenderer.root.find(inputPredicate("auth.integrations.oidc.enabled"))
.props.disabled
testRenderer.root.find(
deprecatedInputPredicate("auth.integrations.oidc.enabled")
).props.disabled
).toBe(true);
await timeout();
expect(
testRenderer.root.find(inputPredicate("auth.integrations.oidc.enabled"))
.props.disabled
testRenderer.root.find(
deprecatedInputPredicate("auth.integrations.oidc.enabled")
).props.disabled
).toBe(false);
});
@@ -1,10 +1,10 @@
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import { ERROR_CODES } from "talk-common/errors";
import { pureMerge } from "talk-common/utils";
import { InvalidRequestError } from "talk-framework/lib/errors";
import { GQLResolver } from "talk-framework/schema";
import {
createSinonStub,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
waitForElement,
@@ -18,27 +18,27 @@ beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/general");
});
const createTestRenderer = async (
resolver: any = {},
options: { muteNetworkErrors?: boolean } = {}
) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
viewer: sinon.stub().returns(users.admins[0]),
},
};
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
const viewer = users.admins[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer, context } = create({
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const configureContainer = await waitForElement(() =>
@@ -51,12 +51,13 @@ const createTestRenderer = async (
"configure-sideBar-saveChanges"
);
return {
context,
testRenderer,
configureContainer,
generalContainer,
saveChangesButton,
};
};
}
it("renders configure general", async () => {
const { configureContainer } = await createTestRenderer();
@@ -64,28 +65,25 @@ it("renders configure general", async () => {
});
it("change site wide commenting", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.disableCommenting).toEqual({
enabled: true,
message: "Closing message",
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.disableCommenting).toEqual({
enabled: true,
message: "Closing message",
});
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
resolvers,
});
const sitewideCommentingContainer = within(generalContainer).getAllByText(
@@ -121,34 +119,32 @@ it("change site wide commenting", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
it("change community guidlines", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.communityGuidelines.content).toEqual(
"This is the community guidlines summary"
);
expectAndFail(data.input.settings.communityGuidelines.enabled).toEqual(
true
);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.communityGuidelines!.content).toEqual(
"This is the community guidlines summary"
);
expectAndFail(variables.settings.communityGuidelines!.enabled).toEqual(
true
);
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
resolvers,
});
const guidelinesContainer = within(generalContainer).getAllByText(
@@ -182,32 +178,27 @@ it("change community guidlines", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
it("change closed stream message", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.closeCommenting.message).toEqual(
"The stream has been closed"
);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.closeCommenting!.message).toEqual(
"The stream has been closed"
);
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
} = await createTestRenderer({ resolvers });
const contentField = within(generalContainer).getByLabelText(
"Closed Stream Message"
@@ -226,33 +217,28 @@ it("change closed stream message", async () => {
// Wait for submission to be finished
await wait(() => {
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
});
it("change comment editing time", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.editCommentWindowLength).toEqual(
108000
);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.editCommentWindowLength).toEqual(
108000
);
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
} = await createTestRenderer({ resolvers });
const durationFieldset = within(generalContainer).getByText(
"Comment Edit Timeframe",
@@ -296,34 +282,31 @@ it("change comment editing time", async () => {
// Wait for submission to be finished
await wait(() => {
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
});
it("change comment length limitations", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.charCount).toEqual({
enabled: true,
min: null,
max: 3000,
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.charCount).toEqual({
enabled: true,
min: null,
max: 3000,
});
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
resolvers,
});
const commentLengthContainer = within(generalContainer).getByText(
@@ -389,33 +372,28 @@ it("change comment length limitations", async () => {
expect(minField.props.disabled).toBe(false);
expect(maxField.props.disabled).toBe(false);
});
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
it("change closing comment streams", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.closeCommenting.auto).toEqual(true);
expectAndFail(data.input.settings.closeCommenting.timeout).toEqual(
2592000
);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.closeCommenting!.auto).toEqual(true);
expectAndFail(variables.settings.closeCommenting!.timeout).toEqual(
2592000
);
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
} = await createTestRenderer({ resolvers });
const closingCommentStreamsContainer = within(generalContainer).getByText(
"Closing Comment Streams",
@@ -464,23 +442,22 @@ it("change closing comment streams", async () => {
expect(valueField.props.disabled).toBe(false);
expect(unitField.props.disabled).toBe(false);
});
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
it("handle server error", async () => {
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
throw new InvalidRequestError({ code: ERROR_CODES.INTERNAL_ERROR });
})
);
const { configureContainer, generalContainer } = await createTestRenderer(
{
Mutation: {
updateSettings: updateSettingsStub,
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: () => {
throw new InvalidRequestError({ code: ERROR_CODES.INTERNAL_ERROR });
},
},
{ muteNetworkErrors: true }
);
});
const { configureContainer, generalContainer } = await createTestRenderer({
resolvers,
muteNetworkErrors: true,
});
const contentField = within(generalContainer).getByLabelText(
"Closed Stream Message"
@@ -1,8 +1,8 @@
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import { GQLResolver } from "talk-framework/schema";
import {
createSinonStub,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
waitForElement,
@@ -16,23 +16,27 @@ beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/moderation");
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
viewer: sinon.stub().returns(users.admins[0]),
},
};
const viewer = users.admins[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const configureContainer = await waitForElement(() =>
@@ -50,7 +54,7 @@ const createTestRenderer = async (resolver: any = {}) => {
moderationContainer,
saveChangesButton,
};
};
}
it("renders configure moderation", async () => {
const { configureContainer } = await createTestRenderer();
@@ -58,30 +62,25 @@ it("renders configure moderation", async () => {
});
it("change akismet settings", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.integrations.akismet).toEqual({
enabled: true,
key: "my api key",
site: "https://coralproject.net",
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.integrations!.akismet).toEqual({
enabled: true,
key: "my api key",
site: "https://coralproject.net",
});
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
moderationContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
} = await createTestRenderer({ resolvers });
const akismetContainer = within(moderationContainer).getByText(
"Akismet Spam Detection Filter",
@@ -137,48 +136,41 @@ it("change akismet settings", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
it("change perspective settings", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(
s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.integrations.perspective).toEqual({
doNotStore: false,
enabled: true,
endpoint: "https://custom-endpoint.net",
key: "my api key",
threshold: 0.1,
});
settingsRecord = merge(settingsRecord, data.input.settings);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables, callCount }) => {
switch (callCount) {
case 0:
expectAndFail(variables.settings.integrations!.perspective).toEqual(
{
doNotStore: false,
enabled: true,
endpoint: "https://custom-endpoint.net",
key: "my api key",
threshold: 0.1,
}
);
break;
default:
expectAndFail(
variables.settings.integrations!.perspective!.threshold
).toBeNull();
}
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
settings: pureMerge(settings, variables.settings),
};
}),
s =>
s.onSecondCall().callsFake((_: any, data: any) => {
expectAndFail(
data.input.settings.integrations.perspective.threshold
).toBeNull();
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
},
},
});
const {
configureContainer,
moderationContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
} = await createTestRenderer({ resolvers });
const perspectiveContainer = within(moderationContainer).getByText(
"Perspective Toxic Comment Filter",
@@ -258,7 +250,7 @@ it("change perspective settings", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.calledOnce).toBe(true);
expect(resolvers.Mutation!.updateSettings!.calledOnce).toBe(true);
// Use default threshold.
thresholdField.props.onChange("");
@@ -278,5 +270,5 @@ it("change perspective settings", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.calledTwice).toBe(true);
expect(resolvers.Mutation!.updateSettings!.calledTwice).toBe(true);
});
@@ -1,8 +1,8 @@
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import { GQLResolver } from "talk-framework/schema";
import {
createSinonStub,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
waitForElement,
@@ -16,23 +16,27 @@ beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/organization");
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
viewer: sinon.stub().returns(users.admins[0]),
},
};
const viewer = users.admins[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const configureContainer = await waitForElement(() =>
@@ -50,7 +54,7 @@ const createTestRenderer = async (resolver: any = {}) => {
organizationContainer,
saveChangesButton,
};
};
}
it("renders configure organization", async () => {
const { configureContainer } = await createTestRenderer();
@@ -58,28 +62,23 @@ it("renders configure organization", async () => {
});
it("change organization name", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.organization.name).toEqual(
"Coral Test"
);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.organization!.name).toEqual(
"Coral Test"
);
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
organizationContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
} = await createTestRenderer({ resolvers });
const organizationNameField = within(organizationContainer).getByLabelText(
"Organization Name"
@@ -119,32 +118,27 @@ it("change organization name", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
it("change organization contact email", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.organization.contactEmail).toEqual(
"test@coralproject.net"
);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.organization!.contactEmail).toEqual(
"test@coralproject.net"
);
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
organizationContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
} = await createTestRenderer({ resolvers });
const organizationEmailField = within(organizationContainer).getByLabelText(
"Organization Email"
@@ -184,5 +178,5 @@ it("change organization contact email", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
@@ -1,8 +1,8 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import { GQLUSER_ROLE } from "talk-framework/schema";
import { pureMerge } from "talk-common/utils";
import { GQLResolver, GQLUSER_ROLE } from "talk-framework/schema";
import {
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
waitForElement,
within,
@@ -15,40 +15,43 @@ beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/general");
});
const createTestRenderer = async (
resolver: any = {},
options: { muteNetworkErrors?: boolean } = {}
) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
},
};
const viewer = users.admins[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
return {
testRenderer,
};
};
}
it("denies access to moderators", async () => {
const deniedRoles = [GQLUSER_ROLE.MODERATOR];
for (const r of deniedRoles) {
const { testRenderer } = await createTestRenderer({
Query: {
viewer: sinon.stub().returns({ ...users.admins[0], role: r }),
},
resolvers: createResolversStub<GQLResolver>({
Query: {
viewer: () => ({ ...viewer, role: r }),
},
}),
});
await waitForElement(() =>
within(testRenderer.root).getByText("Sign in with a different account")
@@ -60,9 +63,11 @@ it("allows access to admins", async () => {
const deniedRoles = [GQLUSER_ROLE.ADMIN];
for (const r of deniedRoles) {
const { testRenderer } = await createTestRenderer({
Query: {
viewer: sinon.stub().returns({ ...users.admins[0], role: r }),
},
resolvers: createResolversStub<GQLResolver>({
Query: {
viewer: () => ({ ...viewer, role: r }),
},
}),
});
await waitForElement(() =>
within(testRenderer.root).getByTestID("configure-container")
@@ -1,14 +1,14 @@
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import {
createSinonStub,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import { GQLResolver } from "talk-framework/schema";
import create from "../create";
import { settings, users } from "../fixtures";
@@ -16,23 +16,27 @@ beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/wordList");
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
viewer: sinon.stub().returns(users.admins[0]),
},
};
const viewer = users.admins[0];
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const configureContainer = await waitForElement(() =>
@@ -50,7 +54,7 @@ const createTestRenderer = async (resolver: any = {}) => {
wordListContainer,
saveChangesButton,
};
};
}
it("renders configure wordList", async () => {
const { configureContainer } = await createTestRenderer();
@@ -58,28 +62,25 @@ it("renders configure wordList", async () => {
});
it("change banned and suspect words", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expectAndFail(data.input.settings.wordList).toEqual({
banned: ["Fuck", "Asshole"],
suspect: ["idiot", "shame"],
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const resolvers = createResolversStub<GQLResolver>({
Mutation: {
updateSettings: ({ variables }) => {
expectAndFail(variables.settings.wordList).toEqual({
banned: ["Fuck", "Asshole"],
suspect: ["idiot", "shame"],
});
return {
settings: pureMerge(settings, variables.settings),
};
},
},
});
const {
configureContainer,
wordListContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
resolvers,
});
const bannedField = within(wordListContainer).getByLabelText(
@@ -110,5 +111,5 @@ it("change banned and suspect words", async () => {
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
});
+7 -62
View File
@@ -1,67 +1,12 @@
import { EventEmitter2 } from "eventemitter2";
import { IResolvers } from "graphql-tools";
import React from "react";
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
import EntryContainer from "talk-admin/containers/EntryContainer";
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
import { PostMessageService } from "talk-framework/lib/postMessage";
import { RestClient } from "talk-framework/lib/rest";
import { createPromisifiedStorage } from "talk-framework/lib/storage";
import { createUUIDGenerator } from "talk-framework/testHelpers";
import { GQLResolver } from "talk-framework/schema";
import {
createTestRenderer,
CreateTestRendererParams,
} from "talk-framework/testHelpers";
import createEnvironment from "./createEnvironment";
import createFluentBundle from "./createFluentBundle";
import createNodeMock from "./createNodeMock";
interface CreateParams {
logNetwork?: boolean;
resolvers?: IResolvers<any, any>;
muteNetworkErrors?: boolean;
initLocalState?: (
local: RecordProxy,
source: RecordSourceProxy,
environment: Environment
) => void;
}
export default function create(params: CreateParams) {
const environment = createEnvironment({
// Set this to true, to see graphql responses.
logNetwork: params.logNetwork,
resolvers: params.resolvers,
muteNetworkErrors: params.muteNetworkErrors,
initLocalState: (localRecord, source, env) => {
if (params.initLocalState) {
params.initLocalState(localRecord, source, env);
}
},
});
const context: TalkContext = {
relayEnvironment: environment,
locales: ["en-US"],
localeBundles: [createFluentBundle()],
localStorage: createPromisifiedStorage(),
sessionStorage: createPromisifiedStorage(),
rest: new RestClient("http://localhost/api"),
postMessage: new PostMessageService(),
browserInfo: { ios: false },
uuidGenerator: createUUIDGenerator(),
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
clearSession: () => Promise.resolve(),
};
let testRenderer: ReactTestRenderer;
TestRenderer.act(() => {
testRenderer = TestRenderer.create(
<TalkContextProvider value={context}>
<EntryContainer />
</TalkContextProvider>,
{ createNodeMock }
);
});
return { context, testRenderer: testRenderer! };
export default function create(params: CreateTestRendererParams<GQLResolver>) {
return createTestRenderer<GQLResolver>("admin", <EntryContainer />, params);
}
@@ -1,30 +0,0 @@
import { IResolvers } from "graphql-tools";
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
import { createRelayEnvironment } from "talk-framework/testHelpers";
interface CreateEnvironmentParams {
logNetwork?: boolean;
resolvers?: IResolvers<any, any>;
muteNetworkErrors?: boolean;
initLocalState?: (
local: RecordProxy,
source: RecordSourceProxy,
environment: Environment
) => void;
}
export default function createEnvironment(params: CreateEnvironmentParams) {
return createRelayEnvironment({
network: {
logNetwork: params.logNetwork,
muteNetworkErrors: params.muteNetworkErrors,
resolvers: params.resolvers || {},
projectName: "tenant",
},
initLocalState: (localRecord, source, environment) => {
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
}
@@ -1,10 +0,0 @@
import path from "path";
import { createFluentBundle } from "talk-framework/testHelpers";
export default function create() {
return createFluentBundle(
"admin",
path.resolve(__dirname, "../../../../locales/en-US")
);
}
@@ -1,13 +0,0 @@
import { noop } from "lodash";
import { ReactElement } from "react";
export default function createNodeMock(element: ReactElement<any>) {
if (element.type === "div") {
return {
innerHtml: "",
className: "",
focus: noop,
};
}
return null;
}
@@ -1,8 +1,12 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import {
createSinonStub,
GQLResolver,
UserToCommentModerationActionHistoryResolver,
} from "talk-framework/schema";
import {
createQueryResolverStub,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
toJSON,
waitForElement,
@@ -17,71 +21,73 @@ beforeEach(async () => {
replaceHistoryLocation("http://localhost/admin/configure/auth");
});
const commentModerationActionHistory = createSinonStub(
s => s.throws(),
s =>
s.withArgs({ first: 5 }).returns({
edges: [
{
node: moderationActions[0],
cursor: moderationActions[0].createdAt,
},
{
node: moderationActions[1],
cursor: moderationActions[1].createdAt,
},
],
pageInfo: {
endCursor: moderationActions[1].createdAt,
hasNextPage: true,
},
}),
s =>
s
.withArgs({
first: 10,
after: moderationActions[1].createdAt,
})
.returns({
edges: [
{
node: moderationActions[2],
cursor: moderationActions[2].createdAt,
},
],
pageInfo: {
endCursor: moderationActions[2].createdAt,
hasNextPage: false,
},
})
);
const viewer = users.admins[0];
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
viewer: sinon.stub().returns({
...users.admins[0],
commentModerationActionHistory,
}),
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
},
};
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () =>
pureMerge(viewer, {
commentModerationActionHistory: createQueryResolverStub<
UserToCommentModerationActionHistoryResolver
>(({ variables }) => {
if (variables.first === 5) {
return {
edges: [
{
node: moderationActions[0],
cursor: moderationActions[0].createdAt,
},
{
node: moderationActions[1],
cursor: moderationActions[1].createdAt,
},
],
pageInfo: {
endCursor: moderationActions[1].createdAt,
hasNextPage: true,
},
};
}
expectAndFail(variables).toEqual({
first: 10,
after: moderationActions[1].createdAt,
});
return {
edges: [
{
node: moderationActions[2],
cursor: moderationActions[2].createdAt,
},
],
pageInfo: {
endCursor: moderationActions[2].createdAt,
hasNextPage: false,
},
};
}),
}),
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const { getByTestID } = within(testRenderer.root);
await waitForElement(() => getByTestID("decisionHistory-toggle"));
return testRenderer;
};
}
async function createTestRendererAndOpenPopover() {
const testRenderer = await createTestRenderer();
@@ -1,8 +1,17 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import {
createSinonStub,
GQLCOMMENT_STATUS,
GQLResolver,
ModerationQueueToCommentsResolver,
MutationToAcceptCommentResolver,
MutationToRejectCommentResolver,
QueryToCommentResolver,
} from "talk-framework/schema";
import {
createMutationResolverStub,
createQueryResolverStub,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
toJSON,
waitForElement,
@@ -20,43 +29,37 @@ import {
users,
} from "../fixtures";
const viewer = users.admins[0];
beforeEach(async () => {
replaceHistoryLocation("http://localhost/admin/moderate");
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
moderationQueues: sinon
.stub()
.returns(
merge(
{},
emptyModerationQueues,
get(resolver, "Query.moderationQueues")
)
),
comments:
get(resolver, "Query.comments") ||
sinon.stub().returns(emptyRejectedComments),
viewer: sinon.stub().returns(users.admins[0]),
},
};
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
moderationQueues: () => emptyModerationQueues,
comments: () => emptyRejectedComments,
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
return testRenderer;
};
}
describe("navigation bar", () => {
it("renders navigation bar (empty queues)", async () => {
@@ -78,48 +81,16 @@ describe("reported queue", () => {
it("renders reported queue with comments", async () => {
const testRenderer = await createTestRenderer({
Query: {
moderationQueues: {
reported: {
count: 2,
comments: sinon.stub().callsFake(data => {
expectAndFail(data).toEqual({ first: 5 });
return {
edges: [
{
node: reportedComments[0],
cursor: reportedComments[0].createdAt,
},
{
node: reportedComments[1],
cursor: reportedComments[1].createdAt,
},
],
pageInfo: {
endCursor: reportedComments[1].createdAt,
hasNextPage: false,
},
};
}),
},
},
},
});
const { getByTestID } = within(testRenderer.root);
await waitForElement(() => getByTestID("moderate-container"));
expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot();
});
it("renders reported queue with comments and load more", async () => {
const testRenderer = await createTestRenderer({
Query: {
moderationQueues: {
reported: {
count: 2,
comments: createSinonStub(
s =>
s.onFirstCall().callsFake(data => {
expectAndFail(data).toEqual({ first: 5 });
resolvers: createResolversStub<GQLResolver>({
Query: {
moderationQueues: () =>
pureMerge(emptyModerationQueues, {
reported: {
count: 2,
comments: createQueryResolverStub<
ModerationQueueToCommentsResolver
>(({ variables }) => {
expectAndFail(variables).toEqual({ first: 5 });
return {
edges: [
{
@@ -133,34 +104,75 @@ describe("reported queue", () => {
],
pageInfo: {
endCursor: reportedComments[1].createdAt,
hasNextPage: true,
},
};
}),
s =>
s.onSecondCall().callsFake(data => {
expectAndFail(data).toEqual({
first: 10,
after: reportedComments[1].createdAt,
});
return {
edges: [
{
node: reportedComments[2],
cursor: reportedComments[2].createdAt,
},
],
pageInfo: {
endCursor: reportedComments[2].createdAt,
hasNextPage: false,
},
};
})
),
},
}) as any,
},
}),
},
}),
});
const { getByTestID } = within(testRenderer.root);
await waitForElement(() => getByTestID("moderate-container"));
expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot();
});
it("renders reported queue with comments and load more", async () => {
const moderationQueuesStub = pureMerge(emptyModerationQueues, {
reported: {
count: 2,
comments: createQueryResolverStub<ModerationQueueToCommentsResolver>(
({ variables, callCount }) => {
switch (callCount) {
case 0:
expectAndFail(variables).toEqual({ first: 5 });
return {
edges: [
{
node: reportedComments[0],
cursor: reportedComments[0].createdAt,
},
{
node: reportedComments[1],
cursor: reportedComments[1].createdAt,
},
],
pageInfo: {
endCursor: reportedComments[1].createdAt,
hasNextPage: true,
},
};
default:
expectAndFail(variables).toEqual({
first: 10,
after: reportedComments[1].createdAt,
});
return {
edges: [
{
node: reportedComments[2],
cursor: reportedComments[2].createdAt,
},
],
pageInfo: {
endCursor: reportedComments[2].createdAt,
hasNextPage: false,
},
};
}
}
) as any,
},
});
const testRenderer = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
moderationQueues: () => moderationQueuesStub,
},
}),
});
const moderateContainer = await waitForElement(() =>
within(testRenderer.root).getByTestID("moderate-container")
);
@@ -194,57 +206,62 @@ describe("reported queue", () => {
});
it("accepts comment in reported queue", async () => {
const acceptCommentStub = sinon.stub().callsFake((_, data) => {
expectAndFail(data).toMatchObject({
input: {
commentID: reportedComments[0].id,
commentRevisionID: reportedComments[0].revision.id,
},
const acceptCommentStub = createMutationResolverStub<
MutationToAcceptCommentResolver
>(({ variables }) => {
expectAndFail(variables).toMatchObject({
commentID: reportedComments[0].id,
commentRevisionID: reportedComments[0].revision.id,
});
return {
comment: {
id: reportedComments[0].id,
status: "ACCEPTED",
status: GQLCOMMENT_STATUS.ACCEPTED,
},
moderationQueues: merge({}, emptyModerationQueues, {
moderationQueues: pureMerge(emptyModerationQueues, {
reported: {
count: 1,
},
}),
clientMutationId: data.input.clientMutationId,
};
});
const testRenderer = await createTestRenderer({
Query: {
moderationQueues: {
reported: {
count: 2,
comments: sinon.stub().callsFake(data => {
expectAndFail(data).toEqual({ first: 5 });
return {
edges: [
{
node: reportedComments[0],
cursor: reportedComments[0].createdAt,
},
{
node: reportedComments[1],
cursor: reportedComments[1].createdAt,
},
],
pageInfo: {
endCursor: reportedComments[1].createdAt,
hasNextPage: false,
const moderationQueuesStub = pureMerge(emptyModerationQueues, {
reported: {
count: 2,
comments: createQueryResolverStub<ModerationQueueToCommentsResolver>(
({ variables }) => {
expectAndFail(variables).toEqual({ first: 5 });
return {
edges: [
{
node: reportedComments[0],
cursor: reportedComments[0].createdAt,
},
};
}),
},
{
node: reportedComments[1],
cursor: reportedComments[1].createdAt,
},
],
pageInfo: {
endCursor: reportedComments[1].createdAt,
hasNextPage: false,
},
};
}
) as any,
},
});
const testRenderer = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
moderationQueues: () => moderationQueuesStub,
},
},
Mutation: {
acceptComment: acceptCommentStub,
},
Mutation: {
acceptComment: acceptCommentStub,
},
}),
});
const testID = `moderate-comment-${reportedComments[0].id}`;
@@ -271,57 +288,61 @@ describe("reported queue", () => {
});
it("rejects comment in reported queue", async () => {
const rejectCommentStub = sinon.stub().callsFake((_, data) => {
expectAndFail(data).toMatchObject({
input: {
commentID: reportedComments[0].id,
commentRevisionID: reportedComments[0].revision.id,
},
const rejectCommentStub = createMutationResolverStub<
MutationToRejectCommentResolver
>(({ variables }) => {
expectAndFail(variables).toMatchObject({
commentID: reportedComments[0].id,
commentRevisionID: reportedComments[0].revision.id,
});
return {
comment: {
id: reportedComments[0].id,
status: "REJECTED",
status: GQLCOMMENT_STATUS.REJECTED,
},
moderationQueues: merge({}, emptyModerationQueues, {
moderationQueues: pureMerge(emptyModerationQueues, {
reported: {
count: 1,
},
}),
clientMutationId: data.input.clientMutationId,
};
});
const testRenderer = await createTestRenderer({
Query: {
moderationQueues: {
reported: {
count: 2,
comments: sinon.stub().callsFake(data => {
expectAndFail(data).toEqual({ first: 5 });
return {
edges: [
{
node: reportedComments[0],
cursor: reportedComments[0].createdAt,
},
{
node: reportedComments[1],
cursor: reportedComments[1].createdAt,
},
],
pageInfo: {
endCursor: reportedComments[1].createdAt,
hasNextPage: false,
},
};
resolvers: createResolversStub<GQLResolver>({
Query: {
moderationQueues: () =>
pureMerge(emptyModerationQueues, {
reported: {
count: 2,
comments: createQueryResolverStub<
ModerationQueueToCommentsResolver
>(({ variables }) => {
expectAndFail(variables).toEqual({ first: 5 });
return {
edges: [
{
node: reportedComments[0],
cursor: reportedComments[0].createdAt,
},
{
node: reportedComments[1],
cursor: reportedComments[1].createdAt,
},
],
pageInfo: {
endCursor: reportedComments[1].createdAt,
hasNextPage: false,
},
};
}) as any,
},
}),
},
},
},
Mutation: {
rejectComment: rejectCommentStub,
},
Mutation: {
rejectComment: rejectCommentStub,
},
}),
});
const testID = `moderate-comment-${reportedComments[0].id}`;
@@ -355,30 +376,32 @@ describe("rejected queue", () => {
it("renders rejected queue with comments", async () => {
const testRenderer = await createTestRenderer({
Query: {
comments: sinon.stub().callsFake((_, data) => {
expectAndFail(data).toEqual({
first: 5,
status: "REJECTED",
});
return {
edges: [
{
node: rejectedComments[0],
cursor: rejectedComments[0].createdAt,
resolvers: createResolversStub<GQLResolver>({
Query: {
comments: ({ variables }) => {
expectAndFail(variables).toEqual({
first: 5,
status: "REJECTED",
});
return {
edges: [
{
node: rejectedComments[0],
cursor: rejectedComments[0].createdAt,
},
{
node: rejectedComments[1],
cursor: rejectedComments[1].createdAt,
},
],
pageInfo: {
endCursor: rejectedComments[1].createdAt,
hasNextPage: false,
},
{
node: rejectedComments[1],
cursor: rejectedComments[1].createdAt,
},
],
pageInfo: {
endCursor: rejectedComments[1].createdAt,
hasNextPage: false,
},
};
}),
},
};
},
},
}),
});
const { getByTestID } = within(testRenderer.root);
await waitForElement(() => getByTestID("moderate-container"));
@@ -387,53 +410,53 @@ describe("rejected queue", () => {
it("renders rejected queue with comments and load more", async () => {
const testRenderer = await createTestRenderer({
Query: {
comments: createSinonStub(
s =>
s.onFirstCall().callsFake((_, data) => {
expectAndFail(data).toEqual({
first: 5,
status: "REJECTED",
});
return {
edges: [
{
node: rejectedComments[0],
cursor: rejectedComments[0].createdAt,
resolvers: createResolversStub<GQLResolver>({
Query: {
comments: ({ variables, callCount }) => {
switch (callCount) {
case 0:
expectAndFail(variables).toEqual({
first: 5,
status: GQLCOMMENT_STATUS.REJECTED,
});
return {
edges: [
{
node: rejectedComments[0],
cursor: rejectedComments[0].createdAt,
},
{
node: rejectedComments[1],
cursor: rejectedComments[1].createdAt,
},
],
pageInfo: {
endCursor: rejectedComments[1].createdAt,
hasNextPage: true,
},
{
node: rejectedComments[1],
cursor: rejectedComments[1].createdAt,
};
default:
expectAndFail(variables).toEqual({
first: 10,
after: rejectedComments[1].createdAt,
status: GQLCOMMENT_STATUS.REJECTED,
});
return {
edges: [
{
node: rejectedComments[2],
cursor: rejectedComments[2].createdAt,
},
],
pageInfo: {
endCursor: rejectedComments[2].createdAt,
hasNextPage: false,
},
],
pageInfo: {
endCursor: rejectedComments[1].createdAt,
hasNextPage: true,
},
};
}),
s =>
s.onSecondCall().callsFake((_, data) => {
expectAndFail(data).toEqual({
first: 10,
after: rejectedComments[1].createdAt,
status: "REJECTED",
});
return {
edges: [
{
node: rejectedComments[2],
cursor: rejectedComments[2].createdAt,
},
],
pageInfo: {
endCursor: rejectedComments[2].createdAt,
hasNextPage: false,
},
};
})
),
},
};
}
},
},
}),
});
const moderateContainer = await waitForElement(() =>
@@ -469,55 +492,56 @@ describe("rejected queue", () => {
});
it("accepts comment in rejected queue", async () => {
const acceptCommentStub = sinon.stub().callsFake((_, data) => {
expectAndFail(data).toMatchObject({
input: {
commentID: rejectedComments[0].id,
commentRevisionID: rejectedComments[0].revision.id,
},
const acceptCommentStub = createMutationResolverStub<
MutationToAcceptCommentResolver
>(({ variables }) => {
expectAndFail(variables).toMatchObject({
commentID: rejectedComments[0].id,
commentRevisionID: rejectedComments[0].revision.id,
});
return {
comment: {
id: rejectedComments[0].id,
status: "ACCEPTED",
status: GQLCOMMENT_STATUS.ACCEPTED,
},
moderationQueues: merge({}, emptyModerationQueues, {
moderationQueues: pureMerge(emptyModerationQueues, {
reported: {
count: 1,
},
}),
clientMutationId: data.input.clientMutationId,
};
});
const testRenderer = await createTestRenderer({
Query: {
comments: sinon.stub().callsFake((_, data) => {
expectAndFail(data).toEqual({
first: 5,
status: "REJECTED",
});
return {
edges: [
{
node: rejectedComments[0],
cursor: rejectedComments[0].createdAt,
resolvers: createResolversStub<GQLResolver>({
Query: {
comments: ({ variables }) => {
expectAndFail(variables).toEqual({
first: 5,
status: "REJECTED",
});
return {
edges: [
{
node: rejectedComments[0],
cursor: rejectedComments[0].createdAt,
},
{
node: rejectedComments[1],
cursor: rejectedComments[1].createdAt,
},
],
pageInfo: {
endCursor: rejectedComments[1].createdAt,
hasNextPage: false,
},
{
node: rejectedComments[1],
cursor: rejectedComments[1].createdAt,
},
],
pageInfo: {
endCursor: rejectedComments[1].createdAt,
hasNextPage: false,
},
};
}),
},
Mutation: {
acceptComment: acceptCommentStub,
},
};
},
},
Mutation: {
acceptComment: acceptCommentStub,
},
}),
});
const testID = `moderate-comment-${rejectedComments[0].id}`;
@@ -546,10 +570,12 @@ describe("rejected queue", () => {
describe("single comment view", () => {
const comment = rejectedComments[0];
const commentStub = sinon.stub().callsFake((_, data) => {
expectAndFail(data).toEqual({ id: comment.id });
return reportedComments[0];
});
const commentStub = createQueryResolverStub<QueryToCommentResolver>(
({ variables }) => {
expectAndFail(variables).toEqual({ id: comment.id });
return reportedComments[0];
}
);
beforeEach(() => {
replaceHistoryLocation(
@@ -559,8 +585,10 @@ describe("single comment view", () => {
it("renders single comment view", async () => {
const testRenderer = await createTestRenderer({
Query: {
comment: commentStub,
resolvers: {
Query: {
comment: commentStub,
},
},
});
const { getByTestID } = within(testRenderer.root);
@@ -571,29 +599,30 @@ describe("single comment view", () => {
});
it("accepts single comment", async () => {
const acceptCommentStub = sinon.stub().callsFake((_, data) => {
expectAndFail(data).toMatchObject({
input: {
commentID: comment.id,
commentRevisionID: comment.revision.id,
},
const acceptCommentStub = createMutationResolverStub<
MutationToAcceptCommentResolver
>(({ variables }) => {
expectAndFail(variables).toMatchObject({
commentID: comment.id,
commentRevisionID: comment.revision.id,
});
return {
comment: {
id: comment.id,
status: "ACCEPTED",
status: GQLCOMMENT_STATUS.ACCEPTED,
},
moderationQueues: emptyModerationQueues,
clientMutationId: data.input.clientMutationId,
};
});
const testRenderer = await createTestRenderer({
Query: {
comment: commentStub,
},
Mutation: {
acceptComment: acceptCommentStub,
resolvers: {
Query: {
comment: commentStub,
},
Mutation: {
acceptComment: acceptCommentStub,
},
},
});
@@ -609,29 +638,30 @@ describe("single comment view", () => {
});
it("rejects single comment", async () => {
const rejectCommentStub = sinon.stub().callsFake((_, data) => {
expectAndFail(data).toMatchObject({
input: {
commentID: comment.id,
commentRevisionID: comment.revision.id,
},
const rejectCommentStub = createMutationResolverStub<
MutationToRejectCommentResolver
>(({ variables }) => {
expectAndFail(variables).toMatchObject({
commentID: comment.id,
commentRevisionID: comment.revision.id,
});
return {
comment: {
id: comment.id,
status: "REJECTED",
status: GQLCOMMENT_STATUS.REJECTED,
},
moderationQueues: emptyModerationQueues,
clientMutationId: data.input.clientMutationId,
};
});
const testRenderer = await createTestRenderer({
Query: {
comment: commentStub,
},
Mutation: {
rejectComment: rejectCommentStub,
resolvers: {
Query: {
comment: commentStub,
},
Mutation: {
rejectComment: rejectCommentStub,
},
},
});
@@ -1,9 +1,10 @@
import { get, merge } from "lodash";
import TestRenderer from "react-test-renderer";
import sinon from "sinon";
import { pureMerge } from "talk-common/utils";
import {
createSinonStub,
createMutationResolverStub,
createResolversStub,
CreateTestRendererParams,
findParentWithType,
replaceHistoryLocation,
waitForElement,
@@ -11,7 +12,12 @@ import {
within,
} from "talk-framework/testHelpers";
import { GQLSTORY_STATUS } from "talk-framework/schema";
import {
GQLResolver,
GQLSTORY_STATUS,
MutationToCloseStoryResolver,
MutationToOpenStoryResolver,
} from "talk-framework/schema";
import create from "../create";
import {
emptyStories,
@@ -21,38 +27,43 @@ import {
users,
} from "../fixtures";
const viewer = users.admins[0];
beforeEach(async () => {
replaceHistoryLocation("http://localhost/admin/stories");
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
stories: sinon.stub().callsFake((_, data) => {
expectAndFail(data.status).toBeFalsy();
return storyConnection;
}),
viewer: sinon.stub().returns(users.admins[0]),
...resolver.Query,
},
};
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => viewer,
stories: ({ variables }) => {
expectAndFail(variables.status).toBeFalsy();
return storyConnection;
},
},
}),
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
},
});
const container = await waitForElement(() =>
within(testRenderer.root).getByTestID("stories-container")
);
return { testRenderer, container };
};
}
it("renders stories", async () => {
const { container } = await createTestRenderer();
@@ -61,25 +72,30 @@ it("renders stories", async () => {
it("renders empty stories", async () => {
const { container } = await createTestRenderer({
Query: {
users: sinon.stub().returns(emptyStories),
},
resolvers: createResolversStub<GQLResolver>({
Query: {
users: () => emptyStories,
},
}),
});
expect(within(container).toJSON()).toMatchSnapshot();
});
it("filter by status", async () => {
const { container } = await createTestRenderer({
Query: {
stories: createSinonStub(
s => s.onFirstCall().returns(storyConnection),
s =>
s.onSecondCall().callsFake((_, data) => {
expectAndFail(data.status).toBe(GQLSTORY_STATUS.CLOSED);
return emptyStories;
})
),
},
resolvers: createResolversStub<GQLResolver>({
Query: {
stories: ({ variables, callCount }) => {
switch (callCount) {
case 0:
return storyConnection;
default:
expectAndFail(variables.status).toBe(GQLSTORY_STATUS.CLOSED);
return emptyStories;
}
},
},
}),
});
const selectField = within(container).getByLabelText("Search by status");
@@ -100,38 +116,42 @@ it("filter by status", async () => {
it("change story status", async () => {
const story = stories[1];
const openStory = sinon.stub().callsFake((_: any, data: any) => {
expectAndFail(data.input).toMatchObject({
id: story.id,
});
const storyRecord = merge({}, story, {
status: GQLSTORY_STATUS.OPEN,
createdAt: false,
isClosed: false,
});
return {
story: storyRecord,
clientMutationId: data.input.clientMutationId,
};
});
const openStory = createMutationResolverStub<MutationToOpenStoryResolver>(
({ variables }) => {
expectAndFail(variables).toMatchObject({
id: story.id,
});
const storyRecord = pureMerge(story, {
status: GQLSTORY_STATUS.OPEN,
createdAt: false,
isClosed: false,
});
return {
story: storyRecord,
};
}
);
const closeStory = sinon.stub().callsFake((_: any, data: any) => {
expectAndFail(data.input).toMatchObject({
id: story.id,
});
const storyRecord = merge({}, story, {
status: GQLSTORY_STATUS.CLOSED,
createdAt: "2018-11-29T16:01:51.897Z",
isClosed: true,
});
return {
story: storyRecord,
clientMutationId: data.input.clientMutationId,
};
});
const closeStory = createMutationResolverStub<MutationToCloseStoryResolver>(
({ variables }) => {
expectAndFail(variables).toMatchObject({
id: story.id,
});
const storyRecord = pureMerge(story, {
status: GQLSTORY_STATUS.CLOSED,
createdAt: "2018-11-29T16:01:51.897Z",
isClosed: true,
});
return {
story: storyRecord,
};
}
);
const { container } = await createTestRenderer({
Mutation: { openStory, closeStory },
resolvers: {
Mutation: { openStory, closeStory },
},
});
const storyRow = within(container).getByText(story.metadata!.title!, {
@@ -174,23 +194,33 @@ it("change story status", async () => {
it("load more", async () => {
const { container } = await createTestRenderer({
Query: {
stories: createSinonStub(
s =>
s.onFirstCall().returns({
edges: [
{ node: stories[0], cursor: stories[0].createdAt },
{ node: stories[1], cursor: stories[1].createdAt },
],
pageInfo: { endCursor: stories[1].createdAt, hasNextPage: true },
}),
s =>
s.onSecondCall().returns({
edges: [{ node: stories[2], cursor: stories[2].createdAt }],
pageInfo: { endCursor: stories[2].createdAt, hasNextPage: false },
})
),
},
resolvers: createResolversStub<GQLResolver>({
Query: {
stories: ({ callCount }) => {
switch (callCount) {
case 0:
return {
edges: [
{ node: stories[0], cursor: stories[0].createdAt },
{ node: stories[1], cursor: stories[1].createdAt },
],
pageInfo: {
endCursor: stories[1].createdAt,
hasNextPage: true,
},
};
default:
return {
edges: [{ node: stories[2], cursor: stories[2].createdAt }],
pageInfo: {
endCursor: stories[2].createdAt,
hasNextPage: false,
},
};
}
},
},
}),
});
const loadMore = within(container).getByText("Load More");
TestRenderer.act(() => {
@@ -206,16 +236,19 @@ it("load more", async () => {
it("filter by search", async () => {
const { container } = await createTestRenderer({
Query: {
stories: createSinonStub(
s => s.onFirstCall().returns(storyConnection),
s =>
s.onSecondCall().callsFake((_, data) => {
expectAndFail(data.query).toBe("search");
return emptyStories;
})
),
},
resolvers: createResolversStub<GQLResolver>({
Query: {
stories: ({ variables, callCount }) => {
switch (callCount) {
case 0:
return storyConnection;
default:
expectAndFail(variables.query).toBe("search");
return emptyStories;
}
},
},
}),
});
const searchField = within(container).getByLabelText(