diff --git a/package-lock.json b/package-lock.json index 840b00c92..27b87d29d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12117,7 +12117,7 @@ "integrity": "sha1-ETOUSrJHeINHOZVZaIPg05z4hc8=", "dev": true, "requires": { - "intl-pluralrules": "github:projectfluent/IntlPluralRules#module" + "intl-pluralrules": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b" } }, "fluent-langneg": { @@ -12427,8 +12427,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "optional": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", @@ -12449,14 +12448,12 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "optional": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12471,20 +12468,17 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "optional": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "optional": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", @@ -12601,8 +12595,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -12614,7 +12607,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -12629,7 +12621,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -12637,14 +12628,12 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -12663,7 +12652,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, "requires": { "minimist": "0.0.8" } @@ -12744,8 +12732,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", @@ -12757,7 +12744,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, "requires": { "wrappy": "1" } @@ -12843,8 +12829,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "optional": true + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "safer-buffer": { "version": "2.1.2", @@ -12880,7 +12865,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -12900,7 +12884,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -12944,14 +12927,12 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "optional": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", - "optional": true + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" } } }, @@ -13449,9 +13430,9 @@ } }, "graphql-schema-typescript": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/graphql-schema-typescript/-/graphql-schema-typescript-1.2.1.tgz", - "integrity": "sha512-ipZh3Epm/Kqcy6MF5FM6uxwCMFok07q+6qyxFOa7ViRufcjzH9Y3nECmECH5WgqRGl2wR6TmskbZd5qJJrGpoA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/graphql-schema-typescript/-/graphql-schema-typescript-1.2.9.tgz", + "integrity": "sha512-IGbQW8aC7MfXqJuaTZ0zHoqLGHUPHX+skV6qf0buqCbumqKyQ+cy8vqUqQksMr8Y/Ga++sB8cvMSQ6yeUbF6rQ==", "dev": true, "requires": { "yargs": "^11.0.0" @@ -23278,9 +23259,9 @@ "dev": true }, "prettier": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.13.7.tgz", - "integrity": "sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==", + "version": "1.16.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", + "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", "dev": true }, "pretty-error": { @@ -27849,9 +27830,9 @@ "dev": true }, "tslint": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", - "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.15.0.tgz", + "integrity": "sha512-6bIEujKR21/3nyeoX2uBnE8s+tMXCQXhqMmaIPJpHmXJoBJPTLcI7/VHRtUwMhnLVdwLqqY3zmd8Dxqa5CVdJA==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -27860,18 +27841,19 @@ "commander": "^2.12.1", "diff": "^3.2.0", "glob": "^7.1.1", - "js-yaml": "^3.7.0", + "js-yaml": "^3.13.0", "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" }, "dependencies": { "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -27882,21 +27864,22 @@ "path-is-absolute": "^1.0.0" } }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "tslib": "^1.8.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } } } }, "tslint-config-prettier": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.17.0.tgz", - "integrity": "sha512-NKWNkThwqE4Snn4Cm6SZB7lV5RMDDFsBwz6fWUkTxOKGjMx8ycOHnjIbhn7dZd5XmssW3CwqUjlANR6EhP9YQw==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", + "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", "dev": true }, "tslint-loader": { @@ -27924,18 +27907,29 @@ } }, "tslint-react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/tslint-react/-/tslint-react-3.6.0.tgz", - "integrity": "sha512-AIv1QcsSnj7e9pFir6cJ6vIncTqxfqeFF3Lzh8SuuBljueYzEAtByuB6zMaD27BL0xhMEqsZ9s5eHuCONydjBw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tslint-react/-/tslint-react-4.0.0.tgz", + "integrity": "sha512-9fNE0fm9zNDx1+b6hgy8rgDN2WsQLRiIrn3+fbqm0tazBVF6jiaCFAITxmU+WSFWYE03Xhp1joCircXOe1WVAQ==", "dev": true, "requires": { - "tsutils": "^2.13.1" + "tsutils": "^3.9.1" + }, + "dependencies": { + "tsutils": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.10.0.tgz", + "integrity": "sha512-q20XSMq7jutbGB8luhKKsQldRKWvyBO2BGqni3p4yq8Ys9bEP/xQw3KepKmMRt9gJ4lvQSScrihJrcKdKoSU7Q==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } } }, "tsutils": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", - "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, "requires": { "tslib": "^1.8.1" diff --git a/package.json b/package.json index ccc5a9175..f05021afa 100644 --- a/package.json +++ b/package.json @@ -230,7 +230,7 @@ "fluent-langneg": "^0.1.0", "fluent-react": "^0.8.3", "graphql-schema-linter": "^0.2.0", - "graphql-schema-typescript": "^1.2.1", + "graphql-schema-typescript": "^1.2.9", "gulp": "^4.0.0", "gulp-babel": "^8.0.0", "gulp-cli": "^2.0.1", @@ -260,7 +260,7 @@ "postcss-nested": "^4.1.1", "postcss-prepend-imports": "^1.0.1", "postcss-preset-env": "^6.5.0", - "prettier": "^1.13.7", + "prettier": "^1.16.4", "prop-types": "^15.6.2", "pstree.remy": "^1.1.0", "pym.js": "^1.3.2", @@ -294,11 +294,11 @@ "ts-node": "^6.2.0", "tsconfig-paths": "^3.4.2", "tsconfig-paths-webpack-plugin": "^3.1.4", - "tslint": "^5.11.0", - "tslint-config-prettier": "^1.17.0", + "tslint": "^5.15.0", + "tslint-config-prettier": "^1.18.0", "tslint-loader": "^3.6.0", "tslint-plugin-prettier": "^2.0.1", - "tslint-react": "^3.6.0", + "tslint-react": "^4.0.0", "typed-css-modules": "^0.3.4", "typeface-manuale": "0.0.54", "typeface-source-sans-pro": "0.0.54", diff --git a/scripts/generateSchemaTypes.js b/scripts/generateSchemaTypes.js index 8ced16a82..a40ff6b80 100644 --- a/scripts/generateSchemaTypes.js +++ b/scripts/generateSchemaTypes.js @@ -4,16 +4,6 @@ const { getGraphQLConfig } = require("graphql-config"); const path = require("path"); const fs = require("fs"); -function lintAndWrite(files) { - const linter = new Linter({ fix: true }); - - for (const { fileName, types } of files) { - const configuration = Configuration.findConfiguration(null, fileName) - .results; - linter.lint(fileName, types, configuration); - } -} - async function main() { const config = getGraphQLConfig(__dirname); const projects = config.getProjects(); @@ -28,8 +18,8 @@ async function main() { config: { contextType: "TenantContext", importStatements: [ - 'import { Cursor } from "talk-server/models/helpers/connection";', 'import TenantContext from "talk-server/graph/tenant/context";', + 'import { Cursor } from "talk-server/models/helpers/connection";', ], customScalarType: { Cursor: "Cursor", Time: "Date" }, }, @@ -40,7 +30,10 @@ async function main() { __dirname, "../src/core/client/framework/schema/__generated__/types.ts" ), - config: {}, + config: { + smartTResult: true, + smartTParent: true, + }, }, ]; @@ -55,16 +48,15 @@ async function main() { } // Create the types for this file. - file.types = await generateTSTypesAsString(schema, { + const types = await generateTSTypesAsString(schema, { tabSpaces: 2, typePrefix: "GQL", strictNulls: false, ...file.config, }); - } - // Send the files off to the linter to be linted and written. - lintAndWrite(files); + fs.writeFileSync(file.fileName, types); + } return files; } diff --git a/src/core/client/admin/routes/community/containers/UserStatusChangeContainer.tsx b/src/core/client/admin/routes/community/containers/UserStatusChangeContainer.tsx index fbfba459e..54342c2d6 100644 --- a/src/core/client/admin/routes/community/containers/UserStatusChangeContainer.tsx +++ b/src/core/client/admin/routes/community/containers/UserStatusChangeContainer.tsx @@ -22,42 +22,30 @@ const UserStatusChangeContainer: StatelessComponent = props => { const banUser = useMutation(BanUserMutation); const removeUserBan = useMutation(RemoveUserBanMutation); const [showBanned, setShowBanned] = useState(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 ( diff --git a/src/core/client/admin/routes/configure/containers/NavigationWarningContainer.tsx b/src/core/client/admin/routes/configure/containers/NavigationWarningContainer.tsx index 27c173781..7011dde4c 100644 --- a/src/core/client/admin/routes/configure/containers/NavigationWarningContainer.tsx +++ b/src/core/client/admin/routes/configure/containers/NavigationWarningContainer.tsx @@ -23,8 +23,8 @@ class NavigationWarningContainer extends React.Component { "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 ); } diff --git a/src/core/client/admin/routes/configure/sections/advanced/components/CustomCSSConfig.tsx b/src/core/client/admin/routes/configure/sections/advanced/components/CustomCSSConfig.tsx index 7181445b2..4c9b2eb52 100644 --- a/src/core/client/admin/routes/configure/sections/advanced/components/CustomCSSConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/advanced/components/CustomCSSConfig.tsx @@ -48,12 +48,11 @@ const CustomCSSConfig: StatelessComponent = ({ disabled }) => ( spellCheck={false} fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/advanced/components/PermittedDomainsConfig.tsx b/src/core/client/admin/routes/configure/sections/advanced/components/PermittedDomainsConfig.tsx index c4f19df86..115f7be73 100644 --- a/src/core/client/admin/routes/configure/sections/advanced/components/PermittedDomainsConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/advanced/components/PermittedDomainsConfig.tsx @@ -49,12 +49,11 @@ const PermittedDomainsConfig: StatelessComponent = ({ disabled }) => ( spellCheck={false} fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/advanced/containers/AdvancedConfigContainer.tsx b/src/core/client/admin/routes/configure/sections/advanced/containers/AdvancedConfigContainer.tsx index ad5654b30..253372332 100644 --- a/src/core/client/admin/routes/configure/sections/advanced/containers/AdvancedConfigContainer.tsx +++ b/src/core/client/admin/routes/configure/sections/advanced/containers/AdvancedConfigContainer.tsx @@ -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 { } private handleOnInitValues = (values: any) => { - this.initialValues = merge({}, this.initialValues, values); + this.initialValues = pureMerge(this.initialValues, values); }; public render() { diff --git a/src/core/client/admin/routes/configure/sections/auth/components/ClientIDField.tsx b/src/core/client/admin/routes/configure/sections/auth/components/ClientIDField.tsx index 506f2fe38..34e683f65 100644 --- a/src/core/client/admin/routes/configure/sections/auth/components/ClientIDField.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/components/ClientIDField.tsx @@ -36,12 +36,11 @@ const ClientSecretField: StatelessComponent = ({ autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/auth/components/ClientSecretField.tsx b/src/core/client/admin/routes/configure/sections/auth/components/ClientSecretField.tsx index 1d843fc7d..d1e773d34 100644 --- a/src/core/client/admin/routes/configure/sections/auth/components/ClientSecretField.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/components/ClientSecretField.tsx @@ -41,12 +41,11 @@ const ClientSecretField: StatelessComponent = ({ autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/auth/components/FacebookConfig.tsx b/src/core/client/admin/routes/configure/sections/auth/components/FacebookConfig.tsx index d8f5edd6f..091cabacb 100644 --- a/src/core/client/admin/routes/configure/sections/auth/components/FacebookConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/components/FacebookConfig.tsx @@ -55,7 +55,8 @@ const FacebookConfig: StatelessComponent = ({ > To enable the integration with Facebook Authentication, you need to - create and set up a web application. For more information visit:
+ create and set up a web application. For more information visit: +
{"https://developers.facebook.com/docs/facebook-login/web"}
diff --git a/src/core/client/admin/routes/configure/sections/auth/components/GoogleConfig.tsx b/src/core/client/admin/routes/configure/sections/auth/components/GoogleConfig.tsx index 7d6d3014d..0708917d2 100644 --- a/src/core/client/admin/routes/configure/sections/auth/components/GoogleConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/components/GoogleConfig.tsx @@ -53,7 +53,8 @@ const GoogleConfig: StatelessComponent = ({ disabled, callbackURL }) => ( > To enable the integration with Google Authentication you need to - create and set up a web application. For more information visit:
+ create and set up a web application. For more information visit: +
{ "https://developers.google.com/identity/protocols/OAuth2WebServer#creatingcred" } diff --git a/src/core/client/admin/routes/configure/sections/auth/components/OIDCConfig.tsx b/src/core/client/admin/routes/configure/sections/auth/components/OIDCConfig.tsx index 6ee6200e9..174baba85 100644 --- a/src/core/client/admin/routes/configure/sections/auth/components/OIDCConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/components/OIDCConfig.tsx @@ -106,12 +106,11 @@ const OIDCConfig: StatelessComponent = ({ autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} @@ -168,12 +167,11 @@ const OIDCConfig: StatelessComponent = ({ Discover - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} @@ -201,12 +199,11 @@ const OIDCConfig: StatelessComponent = ({ autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} @@ -234,12 +231,11 @@ const OIDCConfig: StatelessComponent = ({ autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} @@ -267,12 +263,11 @@ const OIDCConfig: StatelessComponent = ({ autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/auth/containers/AuthConfigContainer.tsx b/src/core/client/admin/routes/configure/sections/auth/containers/AuthConfigContainer.tsx index 6617671f5..2c2d1b63e 100644 --- a/src/core/client/admin/routes/configure/sections/auth/containers/AuthConfigContainer.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/containers/AuthConfigContainer.tsx @@ -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 { }; private handleOnInitValues = (values: any) => { - this.initialValues = merge({}, this.initialValues, values); + this.initialValues = pureMerge(this.initialValues, values); }; public render() { diff --git a/src/core/client/admin/routes/configure/sections/general/components/ClosedStreamMessageConfig.tsx b/src/core/client/admin/routes/configure/sections/general/components/ClosedStreamMessageConfig.tsx index 2d1acf433..b2a7bae35 100644 --- a/src/core/client/admin/routes/configure/sections/general/components/ClosedStreamMessageConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/general/components/ClosedStreamMessageConfig.tsx @@ -46,12 +46,11 @@ const ClosedStreamMessageConfig: StatelessComponent = ({ disabled }) => ( value={input.value} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/general/components/ClosingCommentStreamsConfig.tsx b/src/core/client/admin/routes/configure/sections/general/components/ClosingCommentStreamsConfig.tsx index be4b2b757..e1223f422 100644 --- a/src/core/client/admin/routes/configure/sections/general/components/ClosingCommentStreamsConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/general/components/ClosingCommentStreamsConfig.tsx @@ -72,12 +72,11 @@ const ClosingCommentStreamsConfig: StatelessComponent = ({ value={input.value} disabled={disabled} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/general/components/CommentEditingConfig.tsx b/src/core/client/admin/routes/configure/sections/general/components/CommentEditingConfig.tsx index 499c0df03..3474beca7 100644 --- a/src/core/client/admin/routes/configure/sections/general/components/CommentEditingConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/general/components/CommentEditingConfig.tsx @@ -63,12 +63,11 @@ const CommentEditingConfig: StatelessComponent = ({ disabled }) => ( value={input.value} disabled={disabled} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/general/components/CommentLengthConfig.tsx b/src/core/client/admin/routes/configure/sections/general/components/CommentLengthConfig.tsx index 0f1a80426..8c1e6ed89 100644 --- a/src/core/client/admin/routes/configure/sections/general/components/CommentLengthConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/general/components/CommentLengthConfig.tsx @@ -97,12 +97,11 @@ const CommentLengthConfig: StatelessComponent = ({ disabled }) => ( textAlignCenter /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} @@ -148,12 +147,11 @@ const CommentLengthConfig: StatelessComponent = ({ disabled }) => ( textAlignCenter /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/general/components/GuidelinesConfig.tsx b/src/core/client/admin/routes/configure/sections/general/components/GuidelinesConfig.tsx index 243767b78..6aa2d926d 100644 --- a/src/core/client/admin/routes/configure/sections/general/components/GuidelinesConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/general/components/GuidelinesConfig.tsx @@ -68,12 +68,11 @@ const GuidelinesConfig: StatelessComponent = ({ disabled }) => ( value={input.value} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/general/components/SitewideCommentingConfig.tsx b/src/core/client/admin/routes/configure/sections/general/components/SitewideCommentingConfig.tsx index 9afc507ac..5aadd887a 100644 --- a/src/core/client/admin/routes/configure/sections/general/components/SitewideCommentingConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/general/components/SitewideCommentingConfig.tsx @@ -81,12 +81,11 @@ const SitewideCommentingConfig: StatelessComponent = ({ disabled }) => ( value={input.value} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/general/containers/GeneralConfigContainer.tsx b/src/core/client/admin/routes/configure/sections/general/containers/GeneralConfigContainer.tsx index abce5c7aa..37dde46c6 100644 --- a/src/core/client/admin/routes/configure/sections/general/containers/GeneralConfigContainer.tsx +++ b/src/core/client/admin/routes/configure/sections/general/containers/GeneralConfigContainer.tsx @@ -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 { } private handleOnInitValues = (values: any) => { - this.initialValues = merge({}, this.initialValues, values); + this.initialValues = pureMerge(this.initialValues, values); }; public render() { diff --git a/src/core/client/admin/routes/configure/sections/moderation/components/APIKeyField.tsx b/src/core/client/admin/routes/configure/sections/moderation/components/APIKeyField.tsx index 33bae0383..cbbc3d774 100644 --- a/src/core/client/admin/routes/configure/sections/moderation/components/APIKeyField.tsx +++ b/src/core/client/admin/routes/configure/sections/moderation/components/APIKeyField.tsx @@ -39,12 +39,11 @@ const APIKeyField: StatelessComponent = ({ autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/moderation/components/AkismetConfig.tsx b/src/core/client/admin/routes/configure/sections/moderation/components/AkismetConfig.tsx index dd39120e2..6eeef37e0 100644 --- a/src/core/client/admin/routes/configure/sections/moderation/components/AkismetConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/moderation/components/AkismetConfig.tsx @@ -107,12 +107,11 @@ const AkismetConfig: StatelessComponent = ({ disabled }) => { autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/moderation/components/PerspectiveConfig.tsx b/src/core/client/admin/routes/configure/sections/moderation/components/PerspectiveConfig.tsx index d078bac69..01b024dd3 100644 --- a/src/core/client/admin/routes/configure/sections/moderation/components/PerspectiveConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/moderation/components/PerspectiveConfig.tsx @@ -116,12 +116,11 @@ const PerspectiveConfig: StatelessComponent = ({ disabled }) => { placeholder={TOXICITY_DEFAULT.toString()} textAlignCenter /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} @@ -197,12 +196,11 @@ const PerspectiveConfig: StatelessComponent = ({ disabled }) => { autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/moderation/containers/ModerationConfigContainer.tsx b/src/core/client/admin/routes/configure/sections/moderation/containers/ModerationConfigContainer.tsx index fcd44eea5..3e7b7e2e6 100644 --- a/src/core/client/admin/routes/configure/sections/moderation/containers/ModerationConfigContainer.tsx +++ b/src/core/client/admin/routes/configure/sections/moderation/containers/ModerationConfigContainer.tsx @@ -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 { } private handleOnInitValues = (values: any) => { - this.initialValues = merge({}, this.initialValues, values); + this.initialValues = pureMerge(this.initialValues, values); }; public render() { diff --git a/src/core/client/admin/routes/configure/sections/organization/components/OrganizationContactEmailConfig.tsx b/src/core/client/admin/routes/configure/sections/organization/components/OrganizationContactEmailConfig.tsx index a42f4a543..ce18a94fc 100644 --- a/src/core/client/admin/routes/configure/sections/organization/components/OrganizationContactEmailConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/organization/components/OrganizationContactEmailConfig.tsx @@ -50,12 +50,11 @@ const OrganizationNameConfig: StatelessComponent = ({ disabled }) => ( spellCheck={false} fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/organization/components/OrganizationNameConfig.tsx b/src/core/client/admin/routes/configure/sections/organization/components/OrganizationNameConfig.tsx index 101183a76..8ea455074 100644 --- a/src/core/client/admin/routes/configure/sections/organization/components/OrganizationNameConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/organization/components/OrganizationNameConfig.tsx @@ -53,12 +53,11 @@ const OrganizationNameConfig: StatelessComponent = ({ disabled }) => ( spellCheck={false} fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/organization/containers/OrganizationConfigContainer.tsx b/src/core/client/admin/routes/configure/sections/organization/containers/OrganizationConfigContainer.tsx index ac7897be9..16128c2c7 100644 --- a/src/core/client/admin/routes/configure/sections/organization/containers/OrganizationConfigContainer.tsx +++ b/src/core/client/admin/routes/configure/sections/organization/containers/OrganizationConfigContainer.tsx @@ -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 { } private handleOnInitValues = (values: any) => { - this.initialValues = merge({}, this.initialValues, values); + this.initialValues = pureMerge(this.initialValues, values); }; public render() { diff --git a/src/core/client/admin/routes/configure/sections/wordList/components/WordListTextArea.tsx b/src/core/client/admin/routes/configure/sections/wordList/components/WordListTextArea.tsx index a55188828..0ee5929eb 100644 --- a/src/core/client/admin/routes/configure/sections/wordList/components/WordListTextArea.tsx +++ b/src/core/client/admin/routes/configure/sections/wordList/components/WordListTextArea.tsx @@ -47,12 +47,11 @@ const WordListTextArea: StatelessComponent = ({ autoCapitalize="off" spellCheck={false} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/configure/sections/wordList/containers/WordListConfigContainer.tsx b/src/core/client/admin/routes/configure/sections/wordList/containers/WordListConfigContainer.tsx index 76c1b54c7..e31743f27 100644 --- a/src/core/client/admin/routes/configure/sections/wordList/containers/WordListConfigContainer.tsx +++ b/src/core/client/admin/routes/configure/sections/wordList/containers/WordListConfigContainer.tsx @@ -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 { } private handleOnInitValues = (values: any) => { - this.initialValues = merge({}, this.initialValues, values); + this.initialValues = pureMerge(this.initialValues, values); }; public render() { diff --git a/src/core/client/admin/routes/login/components/EmailField.tsx b/src/core/client/admin/routes/login/components/EmailField.tsx index 43eae4edc..7e9a9756c 100644 --- a/src/core/client/admin/routes/login/components/EmailField.tsx +++ b/src/core/client/admin/routes/login/components/EmailField.tsx @@ -44,12 +44,11 @@ const EmailField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/login/views/addEmailAddress/components/ConfirmEmailField.tsx b/src/core/client/admin/routes/login/views/addEmailAddress/components/ConfirmEmailField.tsx index 9700891bb..bdc2d8c99 100644 --- a/src/core/client/admin/routes/login/views/addEmailAddress/components/ConfirmEmailField.tsx +++ b/src/core/client/admin/routes/login/views/addEmailAddress/components/ConfirmEmailField.tsx @@ -47,12 +47,11 @@ const ConfirmEmailField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/login/views/addEmailAddress/components/EmailField.tsx b/src/core/client/admin/routes/login/views/addEmailAddress/components/EmailField.tsx index a7d078049..d8fd1e5c4 100644 --- a/src/core/client/admin/routes/login/views/addEmailAddress/components/EmailField.tsx +++ b/src/core/client/admin/routes/login/views/addEmailAddress/components/EmailField.tsx @@ -44,12 +44,11 @@ const EmailField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/login/views/createPassword/components/SetPasswordField.tsx b/src/core/client/admin/routes/login/views/createPassword/components/SetPasswordField.tsx index 9d1c3f39d..f4213a0d6 100644 --- a/src/core/client/admin/routes/login/views/createPassword/components/SetPasswordField.tsx +++ b/src/core/client/admin/routes/login/views/createPassword/components/SetPasswordField.tsx @@ -53,12 +53,11 @@ const SetPasswordField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/login/views/createUsername/components/UsernameField.tsx b/src/core/client/admin/routes/login/views/createUsername/components/UsernameField.tsx index e9b88019f..0a0b88975 100644 --- a/src/core/client/admin/routes/login/views/createUsername/components/UsernameField.tsx +++ b/src/core/client/admin/routes/login/views/createUsername/components/UsernameField.tsx @@ -51,12 +51,11 @@ const CreateUsernameField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/login/views/signIn/components/SignInWithEmail.tsx b/src/core/client/admin/routes/login/views/signIn/components/SignInWithEmail.tsx index cc0d60202..28febbce2 100644 --- a/src/core/client/admin/routes/login/views/signIn/components/SignInWithEmail.tsx +++ b/src/core/client/admin/routes/login/views/signIn/components/SignInWithEmail.tsx @@ -64,12 +64,11 @@ const SignInWithEmail: StatelessComponent = props => { fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/admin/routes/moderate/components/CommentContent.tsx b/src/core/client/admin/routes/moderate/components/CommentContent.tsx index b5ac3ce2d..e07875a8b 100644 --- a/src/core/client/admin/routes/moderate/components/CommentContent.tsx +++ b/src/core/client/admin/routes/moderate/components/CommentContent.tsx @@ -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 ? `${escapeHTML(token)}` : 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 ? `${escapeHTML(token)}` : escapeHTML(token) ) .join(""); } diff --git a/src/core/client/admin/test/auth/accountCompletion.spec.tsx b/src/core/client/admin/test/auth/accountCompletion.spec.tsx index a51cbbf2c..ab458f8b6 100644 --- a/src/core/client/admin/test/auth/accountCompletion.spec.tsx +++ b/src/core/client/admin/test/auth/accountCompletion.spec.tsx @@ -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 = {} ) { 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({ + Query: { + settings: () => settings, + viewer: () => + pureMerge(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({ + Query: { + viewer: () => + pureMerge(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({ + Query: { + settings: () => settings, + moderationQueues: () => emptyModerationQueues, + viewer: () => + pureMerge(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({ + Query: { + viewer: () => + pureMerge(viewer, { + email: "hans@test.com", + username: "hans", + profiles: [], + }), + settings: () => + pureMerge(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({ + Query: { + viewer: () => + pureMerge(viewer, { + email: "hans@test.com", + username: "hans", + profiles: [{ __typename: "LocalProfile" }], + }), }, - }, + }), }); await wait(() => expect(window.location.toString()).toBe( diff --git a/src/core/client/admin/test/auth/addEmailAddress.spec.tsx b/src/core/client/admin/test/auth/addEmailAddress.spec.tsx index d3cfcd72f..4b071268d 100644 --- a/src/core/client/admin/test/auth/addEmailAddress.spec.tsx +++ b/src/core/client/admin/test/auth/addEmailAddress.spec.tsx @@ -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 = {} ) { 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({ + Query: { + settings: () => settings, + viewer: () => + pureMerge(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({ + 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({ + 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); }); diff --git a/src/core/client/admin/test/auth/createPassword.spec.tsx b/src/core/client/admin/test/auth/createPassword.spec.tsx index 12d711c63..8d276019b 100644 --- a/src/core/client/admin/test/auth/createPassword.spec.tsx +++ b/src/core/client/admin/test/auth/createPassword.spec.tsx @@ -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 = {} ) { 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({ + Query: { + settings: () => settings, + viewer: () => + pureMerge(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({ + 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({ + 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); }); diff --git a/src/core/client/admin/test/auth/createUsername.spec.tsx b/src/core/client/admin/test/auth/createUsername.spec.tsx index 1cabd816f..27599dde4 100644 --- a/src/core/client/admin/test/auth/createUsername.spec.tsx +++ b/src/core/client/admin/test/auth/createUsername.spec.tsx @@ -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 = {} ) { 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({ + 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({ + 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({ 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); }); diff --git a/src/core/client/admin/test/auth/redirectLoggedIn.spec.tsx b/src/core/client/admin/test/auth/redirectLoggedIn.spec.tsx index 62cf420fb..13494291d 100644 --- a/src/core/client/admin/test/auth/redirectLoggedIn.spec.tsx +++ b/src/core/client/admin/test/auth/redirectLoggedIn.spec.tsx @@ -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 = {} +) { 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({ + 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"); }, }); diff --git a/src/core/client/admin/test/auth/redirectLoggedOut.spec.tsx b/src/core/client/admin/test/auth/redirectLoggedOut.spec.tsx index 35fda04e9..a46bcba63 100644 --- a/src/core/client/admin/test/auth/redirectLoggedOut.spec.tsx +++ b/src/core/client/admin/test/auth/redirectLoggedOut.spec.tsx @@ -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 = {} +) { 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({ + 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"); }); }); diff --git a/src/core/client/admin/test/auth/restricted.spec.tsx b/src/core/client/admin/test/auth/restricted.spec.tsx index 92bbc110e..310ecb54c 100644 --- a/src/core/client/admin/test/auth/restricted.spec.tsx +++ b/src/core/client/admin/test/auth/restricted.spec.tsx @@ -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 = {} +) { 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({ + 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({ + Query: { + viewer: () => + pureMerge(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({ + Query: { + viewer: () => + pureMerge(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(); }); diff --git a/src/core/client/admin/test/auth/signInWithEmail.spec.tsx b/src/core/client/admin/test/auth/signInWithEmail.spec.tsx index 82e9ff8b6..204a52626 100644 --- a/src/core/client/admin/test/auth/signInWithEmail.spec.tsx +++ b/src/core/client/admin/test/auth/signInWithEmail.spec.tsx @@ -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 = {} +) { // 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({ + 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(); diff --git a/src/core/client/admin/test/auth/signOut.spec.tsx b/src/core/client/admin/test/auth/signOut.spec.tsx index 312095d35..d0589a419 100644 --- a/src/core/client/admin/test/auth/signOut.spec.tsx +++ b/src/core/client/admin/test/auth/signOut.spec.tsx @@ -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 = {} +) { + replaceHistoryLocation("http://localhost/admin/moderate/reported"); + const { testRenderer, context } = create({ + ...params, + resolvers: pureMerge( + createResolversStub({ + 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(); }); }); diff --git a/src/core/client/admin/test/community/community.spec.tsx b/src/core/client/admin/test/community/community.spec.tsx index 1388c2bd6..55f66bd52 100644 --- a/src/core/client/admin/test/community/community.spec.tsx +++ b/src/core/client/admin/test/community/community.spec.tsx @@ -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( - () => settings - ), - users: createQueryResolverStub(variables => { - expectAndFail(variables.role).toBeFalsy(); - return communityUsers; - }), - viewer: createQueryResolverStub( - () => users.admins[0] - ), - ...resolver.Query, - }, - }; +const createTestRenderer = async ( + params: CreateTestRendererParams = {} +) => { const { testRenderer } = create({ - // Set this to true, to see graphql responses. - logNetwork: false, - resolvers, - initLocalState: localRecord => { + ...params, + resolvers: pureMerge( + createResolversStub({ + 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( - () => emptyCommunityUsers - ), + resolvers: { + Query: { + users: createQueryResolverStub( + () => 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( - (variables, callCount) => { + resolvers: createResolversStub({ + 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({ + Mutation: { + updateUserRole: ({ variables }) => { + expectAndFail(variables).toMatchObject({ + userID: user.id, + role: GQLUSER_ROLE.STAFF, + }); + const userRecord = pureMerge(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(() => viewer), - }, + resolvers: createResolversStub({ + Query: { + viewer: () => viewer, + }, + }), }); expect(() => within(container).getByLabelText("Change role")).toThrow(); }); it("load more", async () => { const { container } = await createTestRenderer({ - Query: { - users: createQueryResolverStub( - (variables, callCount) => { + resolvers: createResolversStub({ + 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( - (variables, callCount) => { + resolvers: createResolversStub({ + 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( - (variables, callCount) => { + resolvers: createResolversStub({ + 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( - 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({ + Mutation: { + banUser: ({ variables }) => { + expectAndFail(variables).toMatchObject({ + userID: user.id, + }); + const userRecord = pureMerge(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({ + Mutation: { + removeUserBan: ({ variables }) => { + expectAndFail(variables).toMatchObject({ + userID: user.id, + }); + const userRecord = pureMerge(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(() => ({ + 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); }); diff --git a/src/core/client/admin/test/configure/advanced.spec.tsx b/src/core/client/admin/test/configure/advanced.spec.tsx index 0198dd590..839a4601a 100644 --- a/src/core/client/admin/test/configure/advanced.spec.tsx +++ b/src/core/client/admin/test/configure/advanced.spec.tsx @@ -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 = {} +) { + const { testRenderer, context } = create({ + ...params, + resolvers: pureMerge( + createResolversStub({ + 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({ + 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({ + 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({ + 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); }); diff --git a/src/core/client/admin/test/configure/auth.spec.tsx b/src/core/client/admin/test/configure/auth.spec.tsx index 12ebca305..98f389b57 100644 --- a/src/core/client/admin/test/configure/auth.spec.tsx +++ b/src/core/client/admin/test/configure/auth.spec.tsx @@ -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 = {} +) { + const { testRenderer, context } = create({ + ...params, + resolvers: pureMerge( + createResolversStub({ + 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({ + Mutation: { + regenerateSSOKey: () => { return { - settings: merge({}, settingsWithEmptyAuth, { - auth: { - integrations: { - sso: { - key: "==GENERATED_KEY==", - keyGeneratedAt: "2018-11-12T23:26:06.239Z", + settings: pureMerge( + 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({ + 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({ + 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); }); diff --git a/src/core/client/admin/test/configure/general.spec.tsx b/src/core/client/admin/test/configure/general.spec.tsx index 827f66822..0de07b655 100644 --- a/src/core/client/admin/test/configure/general.spec.tsx +++ b/src/core/client/admin/test/configure/general.spec.tsx @@ -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 = {} +) { + const { testRenderer, context } = create({ + ...params, + resolvers: pureMerge( + createResolversStub({ + 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({ + 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({ + 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({ + 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({ + 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({ + 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({ + 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({ + 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" diff --git a/src/core/client/admin/test/configure/moderation.spec.tsx b/src/core/client/admin/test/configure/moderation.spec.tsx index aaca0681b..fcb519043 100644 --- a/src/core/client/admin/test/configure/moderation.spec.tsx +++ b/src/core/client/admin/test/configure/moderation.spec.tsx @@ -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 = {} +) { const { testRenderer } = create({ - // Set this to true, to see graphql responses. - logNetwork: false, - resolvers, - initLocalState: localRecord => { + ...params, + resolvers: pureMerge( + createResolversStub({ + 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({ + 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({ + 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); }); diff --git a/src/core/client/admin/test/configure/organization.spec.tsx b/src/core/client/admin/test/configure/organization.spec.tsx index 065530be3..b5a82fddb 100644 --- a/src/core/client/admin/test/configure/organization.spec.tsx +++ b/src/core/client/admin/test/configure/organization.spec.tsx @@ -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 = {} +) { const { testRenderer } = create({ - // Set this to true, to see graphql responses. - logNetwork: false, - resolvers, - initLocalState: localRecord => { + ...params, + resolvers: pureMerge( + createResolversStub({ + 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({ + 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({ + 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); }); diff --git a/src/core/client/admin/test/configure/permission.spec.tsx b/src/core/client/admin/test/configure/permission.spec.tsx index 3cc0350c8..68f624f46 100644 --- a/src/core/client/admin/test/configure/permission.spec.tsx +++ b/src/core/client/admin/test/configure/permission.spec.tsx @@ -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 = {} +) { const { testRenderer } = create({ - // Set this to true, to see graphql responses. - logNetwork: false, - muteNetworkErrors: options.muteNetworkErrors, - resolvers, - initLocalState: localRecord => { + ...params, + resolvers: pureMerge( + createResolversStub({ + 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({ + 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({ + Query: { + viewer: () => ({ ...viewer, role: r }), + }, + }), }); await waitForElement(() => within(testRenderer.root).getByTestID("configure-container") diff --git a/src/core/client/admin/test/configure/wordList.spec.tsx b/src/core/client/admin/test/configure/wordList.spec.tsx index d0364d27d..6cbc9d12f 100644 --- a/src/core/client/admin/test/configure/wordList.spec.tsx +++ b/src/core/client/admin/test/configure/wordList.spec.tsx @@ -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 = {} +) { const { testRenderer } = create({ - // Set this to true, to see graphql responses. - logNetwork: false, - resolvers, - initLocalState: localRecord => { + ...params, + resolvers: pureMerge( + createResolversStub({ + 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({ + 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); }); diff --git a/src/core/client/admin/test/create.tsx b/src/core/client/admin/test/create.tsx index cde1414b4..5c20c0612 100644 --- a/src/core/client/admin/test/create.tsx +++ b/src/core/client/admin/test/create.tsx @@ -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; - 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( - - - , - { createNodeMock } - ); - }); - - return { context, testRenderer: testRenderer! }; +export default function create(params: CreateTestRendererParams) { + return createTestRenderer("admin", , params); } diff --git a/src/core/client/admin/test/createEnvironment.ts b/src/core/client/admin/test/createEnvironment.ts deleted file mode 100644 index b862f6d2b..000000000 --- a/src/core/client/admin/test/createEnvironment.ts +++ /dev/null @@ -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; - 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); - } - }, - }); -} diff --git a/src/core/client/admin/test/createFluentBundle.ts b/src/core/client/admin/test/createFluentBundle.ts deleted file mode 100644 index 0b883ad53..000000000 --- a/src/core/client/admin/test/createFluentBundle.ts +++ /dev/null @@ -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") - ); -} diff --git a/src/core/client/admin/test/createNodeMock.ts b/src/core/client/admin/test/createNodeMock.ts deleted file mode 100644 index 54abed79a..000000000 --- a/src/core/client/admin/test/createNodeMock.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { noop } from "lodash"; -import { ReactElement } from "react"; - -export default function createNodeMock(element: ReactElement) { - if (element.type === "div") { - return { - innerHtml: "", - className: "", - focus: noop, - }; - } - return null; -} diff --git a/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx b/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx index 39d64fd45..49340f010 100644 --- a/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx +++ b/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx @@ -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 = {} +) { const { testRenderer } = create({ - // Set this to true, to see graphql responses. - logNetwork: false, - resolvers, - initLocalState: localRecord => { + ...params, + resolvers: pureMerge( + createResolversStub({ + 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(); diff --git a/src/core/client/admin/test/moderate/moderate.spec.tsx b/src/core/client/admin/test/moderate/moderate.spec.tsx index 634c9f9c5..e922b3c4b 100644 --- a/src/core/client/admin/test/moderate/moderate.spec.tsx +++ b/src/core/client/admin/test/moderate/moderate.spec.tsx @@ -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 = {} +) { const { testRenderer } = create({ - // Set this to true, to see graphql responses. - logNetwork: false, - resolvers, - initLocalState: localRecord => { + ...params, + resolvers: pureMerge( + createResolversStub({ + 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({ + 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( + ({ 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({ + 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( + ({ 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({ + 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({ + 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({ + 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({ + 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({ + 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( + ({ 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, + }, }, }); diff --git a/src/core/client/admin/test/stories/stories.spec.tsx b/src/core/client/admin/test/stories/stories.spec.tsx index 797a0b500..305ff2452 100644 --- a/src/core/client/admin/test/stories/stories.spec.tsx +++ b/src/core/client/admin/test/stories/stories.spec.tsx @@ -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 = {} +) { const { testRenderer } = create({ - // Set this to true, to see graphql responses. - logNetwork: false, - resolvers, - initLocalState: localRecord => { + ...params, + resolvers: pureMerge( + createResolversStub({ + 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({ + 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({ + 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( + ({ 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( + ({ 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({ + 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({ + Query: { + stories: ({ variables, callCount }) => { + switch (callCount) { + case 0: + return storyConnection; + default: + expectAndFail(variables.query).toBe("search"); + return emptyStories; + } + }, + }, + }), }); const searchField = within(container).getByLabelText( diff --git a/src/core/client/auth/components/ConfirmEmailField.tsx b/src/core/client/auth/components/ConfirmEmailField.tsx index c6be53ef3..f37d1a521 100644 --- a/src/core/client/auth/components/ConfirmEmailField.tsx +++ b/src/core/client/auth/components/ConfirmEmailField.tsx @@ -47,12 +47,11 @@ const ConfirmEmailField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/auth/components/ConfirmPasswordField.tsx b/src/core/client/auth/components/ConfirmPasswordField.tsx index 4e3bdd7ce..be5756e31 100644 --- a/src/core/client/auth/components/ConfirmPasswordField.tsx +++ b/src/core/client/auth/components/ConfirmPasswordField.tsx @@ -49,12 +49,11 @@ const SetPasswordField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/auth/components/EmailField.tsx b/src/core/client/auth/components/EmailField.tsx index 743d8239a..aadfcfe09 100644 --- a/src/core/client/auth/components/EmailField.tsx +++ b/src/core/client/auth/components/EmailField.tsx @@ -44,12 +44,11 @@ const EmailField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/auth/components/SetPasswordField.tsx b/src/core/client/auth/components/SetPasswordField.tsx index 3c4af79e1..2d3a130aa 100644 --- a/src/core/client/auth/components/SetPasswordField.tsx +++ b/src/core/client/auth/components/SetPasswordField.tsx @@ -50,12 +50,11 @@ const SetPasswordField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/auth/components/UsernameField.tsx b/src/core/client/auth/components/UsernameField.tsx index 34a52aa34..7004213fb 100644 --- a/src/core/client/auth/components/UsernameField.tsx +++ b/src/core/client/auth/components/UsernameField.tsx @@ -48,12 +48,11 @@ const CreateUsernameField: StatelessComponent = props => ( fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/auth/test/accountCompletion.spec.tsx b/src/core/client/auth/test/accountCompletion.spec.tsx index 11d1874db..e5382c996 100644 --- a/src/core/client/auth/test/accountCompletion.spec.tsx +++ b/src/core/client/auth/test/accountCompletion.spec.tsx @@ -1,6 +1,7 @@ -import { get, merge } from "lodash"; +import { get } from "lodash"; import sinon from "sinon"; +import { pureMerge } from "talk-common/utils"; import { createAccessToken, wait, @@ -26,11 +27,14 @@ async function createTestRenderer( ...customResolver.Query, settings: sinon .stub() - .returns(merge({}, settings, get(customResolver, "Query.settings"))), + .returns(pureMerge(settings, get(customResolver, "Query.settings"))), viewer: sinon .stub() .returns( - merge({ id: "me", profiles: [] }, get(customResolver, "Query.viewer")) + pureMerge( + { id: "me", profiles: [] }, + get(customResolver, "Query.viewer") + ) ), }, }; diff --git a/src/core/client/auth/test/addEmailAddress.spec.tsx b/src/core/client/auth/test/addEmailAddress.spec.tsx index 2e13c0c00..c11ffb00e 100644 --- a/src/core/client/auth/test/addEmailAddress.spec.tsx +++ b/src/core/client/auth/test/addEmailAddress.spec.tsx @@ -1,6 +1,7 @@ -import { get, merge } from "lodash"; +import { get } from "lodash"; import sinon from "sinon"; +import { pureMerge } from "talk-common/utils"; import { toJSON, wait, @@ -24,7 +25,7 @@ async function createTestRenderer( ...customResolver.Query, settings: sinon .stub() - .returns(merge({}, settings, get(customResolver, "Query.settings"))), + .returns(pureMerge(settings, get(customResolver, "Query.settings"))), }, }; diff --git a/src/core/client/auth/test/create.tsx b/src/core/client/auth/test/create.tsx index 66784df84..67ed4b15d 100644 --- a/src/core/client/auth/test/create.tsx +++ b/src/core/client/auth/test/create.tsx @@ -1,62 +1,11 @@ -import { EventEmitter2 } from "eventemitter2"; -import { IResolvers } from "graphql-tools"; import React from "react"; -import TestRenderer from "react-test-renderer"; -import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime"; import AppQuery from "talk-auth/queries/AppQuery"; -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 { + createTestRenderer, + CreateTestRendererParams, +} from "talk-framework/testHelpers"; -import createEnvironment from "./createEnvironment"; -import createFluentBundle from "./createFluentBundle"; - -interface CreateParams { - logNetwork?: boolean; - muteNetworkErrors?: boolean; - resolvers?: IResolvers; - 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, - muteNetworkErrors: params.muteNetworkErrors, - resolvers: params.resolvers, - 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(), - }; - - const testRenderer = TestRenderer.create( - - - - ); - - return { context, testRenderer }; +export default function create(params: CreateTestRendererParams) { + return createTestRenderer("auth", , params); } diff --git a/src/core/client/auth/test/createEnvironment.ts b/src/core/client/auth/test/createEnvironment.ts deleted file mode 100644 index 4c238cc40..000000000 --- a/src/core/client/auth/test/createEnvironment.ts +++ /dev/null @@ -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; - muteNetworkErrors?: boolean; - resolvers?: IResolvers; - 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); - } - }, - }); -} diff --git a/src/core/client/auth/test/createFluentBundle.ts b/src/core/client/auth/test/createFluentBundle.ts deleted file mode 100644 index 93f9a400d..000000000 --- a/src/core/client/auth/test/createFluentBundle.ts +++ /dev/null @@ -1,10 +0,0 @@ -import path from "path"; - -import { createFluentBundle } from "talk-framework/testHelpers"; - -export default function create() { - return createFluentBundle( - "auth", - path.resolve(__dirname, "../../../../locales/en-US") - ); -} diff --git a/src/core/client/auth/test/createPassword.spec.tsx b/src/core/client/auth/test/createPassword.spec.tsx index 18cbb93aa..567519573 100644 --- a/src/core/client/auth/test/createPassword.spec.tsx +++ b/src/core/client/auth/test/createPassword.spec.tsx @@ -1,6 +1,7 @@ -import { get, merge } from "lodash"; +import { get } from "lodash"; import sinon from "sinon"; +import { pureMerge } from "talk-common/utils"; import { toJSON, wait, @@ -24,7 +25,7 @@ async function createTestRenderer( ...customResolver.Query, settings: sinon .stub() - .returns(merge({}, settings, get(customResolver, "Query.settings"))), + .returns(pureMerge(settings, get(customResolver, "Query.settings"))), }, }; diff --git a/src/core/client/auth/test/createUsername.spec.tsx b/src/core/client/auth/test/createUsername.spec.tsx index 18adae4ab..3561cff26 100644 --- a/src/core/client/auth/test/createUsername.spec.tsx +++ b/src/core/client/auth/test/createUsername.spec.tsx @@ -1,6 +1,7 @@ -import { get, merge } from "lodash"; +import { get } from "lodash"; import sinon from "sinon"; +import { pureMerge } from "talk-common/utils"; import { toJSON, wait, @@ -24,7 +25,7 @@ async function createTestRenderer( ...customResolver.Query, settings: sinon .stub() - .returns(merge({}, settings, get(customResolver, "Query.settings"))), + .returns(pureMerge(settings, get(customResolver, "Query.settings"))), }, }; diff --git a/src/core/client/auth/test/signIn.spec.tsx b/src/core/client/auth/test/signIn.spec.tsx index 981e99d55..78ad80b1a 100644 --- a/src/core/client/auth/test/signIn.spec.tsx +++ b/src/core/client/auth/test/signIn.spec.tsx @@ -1,6 +1,7 @@ -import { get, merge } from "lodash"; +import { get } from "lodash"; import sinon from "sinon"; +import { pureMerge } from "talk-common/utils"; import { toJSON, wait, @@ -24,7 +25,7 @@ async function createTestRenderer( ...customResolver.Query, settings: sinon .stub() - .returns(merge({}, settings, get(customResolver, "Query.settings"))), + .returns(pureMerge(settings, get(customResolver, "Query.settings"))), }, }; diff --git a/src/core/client/auth/views/forgotPassword/components/ForgotPassword.tsx b/src/core/client/auth/views/forgotPassword/components/ForgotPassword.tsx index f44310ded..479124b49 100644 --- a/src/core/client/auth/views/forgotPassword/components/ForgotPassword.tsx +++ b/src/core/client/auth/views/forgotPassword/components/ForgotPassword.tsx @@ -82,12 +82,11 @@ const ForgotPassword: StatelessComponent = props => { disabled={submitting} /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} )} diff --git a/src/core/client/auth/views/signIn/components/SignInWithEmail.tsx b/src/core/client/auth/views/signIn/components/SignInWithEmail.tsx index c4d26ffb1..445970ba9 100644 --- a/src/core/client/auth/views/signIn/components/SignInWithEmail.tsx +++ b/src/core/client/auth/views/signIn/components/SignInWithEmail.tsx @@ -67,12 +67,11 @@ const SignInWithEmail: StatelessComponent = props => { fullWidth /> - {meta.touched && - (meta.error || meta.submitError) && ( - - {meta.error || meta.submitError} - - )} + {meta.touched && (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )}