mirror of
https://github.com/wassname/talk.git
synced 2026-06-27 19:01:24 +08:00
[CORL-331] Better tests with types (#2270)
* feat: suspending, banning, now propogation * feat: new mutation api with hooks support * feat: better types in tests and refactor * fix: lint
This commit is contained in:
Generated
+54
-60
@@ -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"
|
||||
|
||||
+5
-5
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -22,42 +22,30 @@ const UserStatusChangeContainer: StatelessComponent<Props> = props => {
|
||||
const banUser = useMutation(BanUserMutation);
|
||||
const removeUserBan = useMutation(RemoveUserBanMutation);
|
||||
const [showBanned, setShowBanned] = useState<boolean>(false);
|
||||
const handleBan = useCallback(
|
||||
() => {
|
||||
if (props.user.status.ban.active) {
|
||||
return;
|
||||
}
|
||||
setShowBanned(true);
|
||||
},
|
||||
[props.user, setShowBanned]
|
||||
);
|
||||
const handleRemoveBan = useCallback(
|
||||
() => {
|
||||
if (!props.user.status.ban.active) {
|
||||
return;
|
||||
}
|
||||
removeUserBan({ userID: props.user.id });
|
||||
},
|
||||
[props.user, removeUserBan]
|
||||
);
|
||||
const handleSuspend = useCallback(
|
||||
() => {
|
||||
if (props.user.status.suspension.active) {
|
||||
return;
|
||||
}
|
||||
// TODO: (cvle)
|
||||
},
|
||||
[props.user]
|
||||
);
|
||||
const handleRemoveSuspension = useCallback(
|
||||
() => {
|
||||
if (!props.user.status.suspension.active) {
|
||||
return;
|
||||
}
|
||||
// TODO: (cvle)
|
||||
},
|
||||
[props.user]
|
||||
);
|
||||
const handleBan = useCallback(() => {
|
||||
if (props.user.status.ban.active) {
|
||||
return;
|
||||
}
|
||||
setShowBanned(true);
|
||||
}, [props.user, setShowBanned]);
|
||||
const handleRemoveBan = useCallback(() => {
|
||||
if (!props.user.status.ban.active) {
|
||||
return;
|
||||
}
|
||||
removeUserBan({ userID: props.user.id });
|
||||
}, [props.user, removeUserBan]);
|
||||
const handleSuspend = useCallback(() => {
|
||||
if (props.user.status.suspension.active) {
|
||||
return;
|
||||
}
|
||||
// TODO: (cvle)
|
||||
}, [props.user]);
|
||||
const handleRemoveSuspension = useCallback(() => {
|
||||
if (!props.user.status.suspension.active) {
|
||||
return;
|
||||
}
|
||||
// TODO: (cvle)
|
||||
}, [props.user]);
|
||||
|
||||
if (props.user.role !== GQLUSER_ROLE.COMMENTER) {
|
||||
return (
|
||||
|
||||
@@ -23,8 +23,8 @@ class NavigationWarningContainer extends React.Component<Props> {
|
||||
"You have unsaved input. Are you sure you want to leave this page?"
|
||||
);
|
||||
|
||||
this.removeTransitionHook = props.router.addTransitionHook(
|
||||
() => (this.props.active ? warningMessage : true)
|
||||
this.removeTransitionHook = props.router.addTransitionHook(() =>
|
||||
this.props.active ? warningMessage : true
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -48,12 +48,11 @@ const CustomCSSConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
spellCheck={false}
|
||||
fullWidth
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -49,12 +49,11 @@ const PermittedDomainsConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
spellCheck={false}
|
||||
fullWidth
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { AdvancedConfigContainer_settings as SettingsData } from "talk-admin/__generated__/AdvancedConfigContainer_settings.graphql";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import AdvancedConfig from "../components/AdvancedConfig";
|
||||
@@ -28,7 +28,7 @@ class AdvancedConfigContainer extends React.Component<Props> {
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
this.initialValues = pureMerge(this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
|
||||
@@ -36,12 +36,11 @@ const ClientSecretField: StatelessComponent<Props> = ({
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -41,12 +41,11 @@ const ClientSecretField: StatelessComponent<Props> = ({
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -55,7 +55,8 @@ const FacebookConfig: StatelessComponent<Props> = ({
|
||||
>
|
||||
<Typography>
|
||||
To enable the integration with Facebook Authentication, you need to
|
||||
create and set up a web application. For more information visit:<br />
|
||||
create and set up a web application. For more information visit:
|
||||
<br />
|
||||
{"https://developers.facebook.com/docs/facebook-login/web"}
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
@@ -53,7 +53,8 @@ const GoogleConfig: StatelessComponent<Props> = ({ disabled, callbackURL }) => (
|
||||
>
|
||||
<Typography>
|
||||
To enable the integration with Google Authentication you need to
|
||||
create and set up a web application. For more information visit:<br />
|
||||
create and set up a web application. For more information visit:
|
||||
<br />
|
||||
{
|
||||
"https://developers.google.com/identity/protocols/OAuth2WebServer#creatingcred"
|
||||
}
|
||||
|
||||
@@ -106,12 +106,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
@@ -168,12 +167,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
|
||||
Discover
|
||||
</Button>
|
||||
</Flex>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
@@ -201,12 +199,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
@@ -234,12 +231,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
@@ -267,12 +263,11 @@ const OIDCConfig: StatelessComponent<Props> = ({
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+3
-2
@@ -1,11 +1,12 @@
|
||||
import { FORM_ERROR, FormApi } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { RouteProps } from "found";
|
||||
import { get, merge } from "lodash";
|
||||
import { get } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { AuthConfigContainer_auth as AuthData } from "talk-admin/__generated__/AuthConfigContainer_auth.graphql";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { TalkContext, withContext } from "talk-framework/lib/bootstrap";
|
||||
import {
|
||||
AddSubmitHook,
|
||||
@@ -80,7 +81,7 @@ class AuthConfigContainer extends React.Component<Props> {
|
||||
};
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
this.initialValues = pureMerge(this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
|
||||
+5
-6
@@ -46,12 +46,11 @@ const ClosedStreamMessageConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
value={input.value}
|
||||
/>
|
||||
</Suspense>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -72,12 +72,11 @@ const ClosingCommentStreamsConfig: StatelessComponent<Props> = ({
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -63,12 +63,11 @@ const CommentEditingConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+10
-12
@@ -97,12 +97,11 @@ const CommentLengthConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
textAlignCenter
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
@@ -148,12 +147,11 @@ const CommentLengthConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
textAlignCenter
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -68,12 +68,11 @@ const GuidelinesConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
value={input.value}
|
||||
/>
|
||||
</Suspense>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -81,12 +81,11 @@ const SitewideCommentingConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
value={input.value}
|
||||
/>
|
||||
</Suspense>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { GeneralConfigContainer_settings as SettingsData } from "talk-admin/__generated__/GeneralConfigContainer_settings.graphql";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import GeneralConfig from "../components/GeneralConfig";
|
||||
@@ -28,7 +28,7 @@ class GeneralConfigContainer extends React.Component<Props> {
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
this.initialValues = pureMerge(this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
|
||||
+5
-6
@@ -39,12 +39,11 @@ const APIKeyField: StatelessComponent<Props> = ({
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -107,12 +107,11 @@ const AkismetConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+10
-12
@@ -116,12 +116,11 @@ const PerspectiveConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
placeholder={TOXICITY_DEFAULT.toString()}
|
||||
textAlignCenter
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
@@ -197,12 +196,11 @@ const PerspectiveConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { ModerationConfigContainer_settings as SettingsData } from "talk-admin/__generated__/ModerationConfigContainer_settings.graphql";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import ModerationConfig from "../components/ModerationConfig";
|
||||
@@ -28,7 +28,7 @@ class ModerationConfigContainer extends React.Component<Props> {
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
this.initialValues = pureMerge(this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
|
||||
+5
-6
@@ -50,12 +50,11 @@ const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
spellCheck={false}
|
||||
fullWidth
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -53,12 +53,11 @@ const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
spellCheck={false}
|
||||
fullWidth
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { OrganizationConfigContainer_settings as SettingsData } from "talk-admin/__generated__/OrganizationConfigContainer_settings.graphql";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import OrganizationConfig from "../components/OrganizationConfig";
|
||||
@@ -28,7 +28,7 @@ class OrganizationConfigContainer extends React.Component<Props> {
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
this.initialValues = pureMerge(this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
|
||||
+5
-6
@@ -47,12 +47,11 @@ const WordListTextArea: StatelessComponent<Props> = ({
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { WordListConfigContainer_settings as SettingsData } from "talk-admin/__generated__/WordListConfigContainer_settings.graphql";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import WordListConfig from "../components/WordListConfig";
|
||||
@@ -28,7 +28,7 @@ class WordListConfigContainer extends React.Component<Props> {
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
this.initialValues = pureMerge(this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
|
||||
@@ -44,12 +44,11 @@ const EmailField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -47,12 +47,11 @@ const ConfirmEmailField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -44,12 +44,11 @@ const EmailField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -53,12 +53,11 @@ const SetPasswordField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
+5
-6
@@ -51,12 +51,11 @@ const CreateUsernameField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -64,12 +64,11 @@ const SignInWithEmail: StatelessComponent<SignInWithEmailForm> = props => {
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -76,13 +76,12 @@ function markPhrasesHTML(
|
||||
return text;
|
||||
}
|
||||
return tokens
|
||||
.map(
|
||||
(token, i) =>
|
||||
// Using our Regexp patterns it returns tokens arranged this way
|
||||
// [STRING_WITH_NO_MATCH, NEW_WORD_DELIMITER, MATCHED_WORD, ...].
|
||||
// This pattern repeats throughout. Next line will mark MATCHED_WORD
|
||||
// and escape all tokens.
|
||||
i % 3 === 2 ? `<mark>${escapeHTML(token)}</mark>` : escapeHTML(token)
|
||||
.map((token, i) =>
|
||||
// Using our Regexp patterns it returns tokens arranged this way
|
||||
// [STRING_WITH_NO_MATCH, NEW_WORD_DELIMITER, MATCHED_WORD, ...].
|
||||
// This pattern repeats throughout. Next line will mark MATCHED_WORD
|
||||
// and escape all tokens.
|
||||
i % 3 === 2 ? `<mark>${escapeHTML(token)}</mark>` : escapeHTML(token)
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createAccessToken,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -12,39 +13,37 @@ import {
|
||||
import create from "../create";
|
||||
import { emptyModerationQueues, settings, users } from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
customResolver: any = {},
|
||||
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/login");
|
||||
const resolvers = {
|
||||
...customResolver,
|
||||
Query: {
|
||||
...customResolver.Query,
|
||||
moderationQueues: sinon.stub().returns(emptyModerationQueues),
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
|
||||
viewer: sinon
|
||||
.stub()
|
||||
.returns(
|
||||
merge(
|
||||
{ ...users.admins[0], email: "", username: "", profiles: [] },
|
||||
get(customResolver, "Query.viewer")
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: options.logNetwork,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
email: "",
|
||||
username: "",
|
||||
profiles: [],
|
||||
}),
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue("SIGN_IN", "authView");
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
localRecord.setValue(createAccessToken(), "accessToken");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -62,44 +61,60 @@ it("renders addEmailAddress view", async () => {
|
||||
|
||||
it("renders createUsername view", async () => {
|
||||
const { root } = await createTestRenderer({
|
||||
Query: {
|
||||
viewer: {
|
||||
email: "hans@test.com",
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
email: "hans@test.com",
|
||||
username: "",
|
||||
profiles: [],
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
await waitForElement(() => within(root).queryByText("Create Username"));
|
||||
});
|
||||
|
||||
it("renders createPassword view", async () => {
|
||||
const { root } = await createTestRenderer({
|
||||
Query: {
|
||||
viewer: {
|
||||
email: "hans@test.com",
|
||||
username: "hans",
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
email: "hans@test.com",
|
||||
username: "hans",
|
||||
profiles: [],
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
await waitForElement(() => within(root).queryByText("Create Password"));
|
||||
});
|
||||
|
||||
it("do not render createPassword view when local auth is disabled", async () => {
|
||||
await createTestRenderer({
|
||||
Query: {
|
||||
viewer: {
|
||||
email: "hans@test.com",
|
||||
username: "hans",
|
||||
},
|
||||
settings: {
|
||||
auth: {
|
||||
integrations: {
|
||||
local: {
|
||||
enabled: false,
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
email: "hans@test.com",
|
||||
username: "hans",
|
||||
profiles: [],
|
||||
}),
|
||||
settings: () =>
|
||||
pureMerge<typeof settings>(settings, {
|
||||
auth: {
|
||||
integrations: {
|
||||
local: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await wait(() =>
|
||||
@@ -111,13 +126,16 @@ it("do not render createPassword view when local auth is disabled", async () =>
|
||||
|
||||
it("complete account", async () => {
|
||||
await createTestRenderer({
|
||||
Query: {
|
||||
viewer: {
|
||||
email: "hans@test.com",
|
||||
username: "hans",
|
||||
profiles: [{ __typename: "LocalProfile" }],
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
email: "hans@test.com",
|
||||
username: "hans",
|
||||
profiles: [{ __typename: "LocalProfile" }],
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
await wait(() =>
|
||||
expect(window.location.toString()).toBe(
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createAccessToken,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
toJSON,
|
||||
wait,
|
||||
@@ -13,33 +14,37 @@ import {
|
||||
import create from "../create";
|
||||
import { settings, users } from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
customResolver: any = {},
|
||||
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/login");
|
||||
const resolvers = {
|
||||
...customResolver,
|
||||
Query: {
|
||||
...customResolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
|
||||
viewer: sinon.stub().returns({ ...users.admins[0], email: "" }),
|
||||
},
|
||||
};
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: options.logNetwork,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
email: "",
|
||||
}),
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue("ADD_EMAIL_ADDRESS", "authView");
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
localRecord.setValue(createAccessToken(), "accessToken");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const container = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("completeAccountBox")
|
||||
);
|
||||
@@ -94,21 +99,22 @@ it("accepts valid email", async () => {
|
||||
|
||||
it("shows server error", async () => {
|
||||
const email = "hans@test.com";
|
||||
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
|
||||
throw new Error("server error");
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
setEmail: () => {
|
||||
throw new Error("server error");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
form,
|
||||
emailAddressField,
|
||||
confirmEmailAddressField,
|
||||
} = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
setEmail,
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
} = await createTestRenderer({
|
||||
resolvers,
|
||||
muteNetworkErrors: true,
|
||||
});
|
||||
const submitButton = form.find(
|
||||
i => i.type === "button" && i.props.type === "submit"
|
||||
);
|
||||
@@ -130,27 +136,28 @@ it("shows server error", async () => {
|
||||
|
||||
it("successfully sets email", async () => {
|
||||
const email = "hans@test.com";
|
||||
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input).toEqual({
|
||||
email,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
});
|
||||
return {
|
||||
user: {
|
||||
id: "me",
|
||||
email,
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
setEmail: ({ variables }) => {
|
||||
expectAndFail(variables).toEqual({
|
||||
email,
|
||||
});
|
||||
return {
|
||||
user: {
|
||||
id: "me",
|
||||
email,
|
||||
},
|
||||
};
|
||||
},
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
form,
|
||||
emailAddressField,
|
||||
confirmEmailAddressField,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
setEmail,
|
||||
},
|
||||
resolvers,
|
||||
});
|
||||
const submitButton = form.find(
|
||||
i => i.type === "button" && i.props.type === "submit"
|
||||
@@ -169,5 +176,5 @@ it("successfully sets email", async () => {
|
||||
await wait(() => expect(submitButton.props.disabled).toBe(false));
|
||||
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
expect(setEmail.called).toBe(true);
|
||||
expect(resolvers.Mutation!.setEmail!.called).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createAccessToken,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
toJSON,
|
||||
wait,
|
||||
@@ -13,33 +14,37 @@ import {
|
||||
import create from "../create";
|
||||
import { settings, users } from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
customResolver: any = {},
|
||||
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/login");
|
||||
const resolvers = {
|
||||
...customResolver,
|
||||
Query: {
|
||||
...customResolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
|
||||
viewer: sinon.stub().returns({ ...users.admins[0], profiles: [] }),
|
||||
},
|
||||
};
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: options.logNetwork,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
profiles: [],
|
||||
}),
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue("CREATE_PASSWORD", "authView");
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
localRecord.setValue(createAccessToken(), "accessToken");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const container = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("completeAccountBox")
|
||||
);
|
||||
@@ -76,17 +81,18 @@ it("checks for invalid password", async () => {
|
||||
|
||||
it("shows server error", async () => {
|
||||
const password = "secretpassword";
|
||||
const setPassword = sinon.stub().callsFake((_: any, data: any) => {
|
||||
throw new Error("server error");
|
||||
});
|
||||
const { form, passwordField } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
setPassword,
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
setPassword: () => {
|
||||
throw new Error("server error");
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
});
|
||||
|
||||
const { form, passwordField } = await createTestRenderer({
|
||||
resolvers,
|
||||
muteNetworkErrors: true,
|
||||
});
|
||||
const submitButton = form.find(
|
||||
i => i.type === "button" && i.props.type === "submit"
|
||||
);
|
||||
@@ -104,23 +110,23 @@ it("shows server error", async () => {
|
||||
|
||||
it("successfully sets password", async () => {
|
||||
const password = "secretpassword";
|
||||
const setPassword = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input).toEqual({
|
||||
password,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
});
|
||||
return {
|
||||
user: {
|
||||
id: "me",
|
||||
profiles: [],
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
setPassword: ({ variables }) => {
|
||||
expectAndFail(variables).toEqual({
|
||||
password,
|
||||
});
|
||||
return {
|
||||
user: {
|
||||
id: "me",
|
||||
profiles: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
},
|
||||
});
|
||||
const { form, passwordField } = await createTestRenderer({
|
||||
Mutation: {
|
||||
setPassword,
|
||||
},
|
||||
resolvers,
|
||||
});
|
||||
const submitButton = form.find(
|
||||
i => i.type === "button" && i.props.type === "submit"
|
||||
@@ -135,5 +141,5 @@ it("successfully sets password", async () => {
|
||||
await wait(() => expect(submitButton.props.disabled).toBe(false));
|
||||
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
expect(setPassword.called).toBe(true);
|
||||
expect(resolvers.Mutation!.setPassword!.called).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createAccessToken,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
toJSON,
|
||||
wait,
|
||||
@@ -14,29 +15,27 @@ import create from "../create";
|
||||
import { settings } from "../fixtures";
|
||||
|
||||
async function createTestRenderer(
|
||||
customResolver: any = {},
|
||||
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/login");
|
||||
const resolvers = {
|
||||
...customResolver,
|
||||
Query: {
|
||||
...customResolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
|
||||
},
|
||||
};
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: options.logNetwork,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue("CREATE_USERNAME", "authView");
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
localRecord.setValue(createAccessToken(), "accessToken");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const container = await waitForElement(() =>
|
||||
@@ -75,17 +74,17 @@ it("checks for invalid username", async () => {
|
||||
|
||||
it("shows server error", async () => {
|
||||
const username = "hans";
|
||||
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
|
||||
throw new Error("server error");
|
||||
});
|
||||
const { form, usernameField } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
setUsername,
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
setUsername: () => {
|
||||
throw new Error("server error");
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
});
|
||||
const { form, usernameField } = await createTestRenderer({
|
||||
resolvers,
|
||||
muteNetworkErrors: true,
|
||||
});
|
||||
const submitButton = form.find(
|
||||
i => i.type === "button" && i.props.type === "submit"
|
||||
);
|
||||
@@ -103,24 +102,25 @@ it("shows server error", async () => {
|
||||
|
||||
it("successfully sets username", async () => {
|
||||
const username = "hans";
|
||||
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input).toEqual({
|
||||
username,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
});
|
||||
return {
|
||||
user: {
|
||||
id: "me",
|
||||
username,
|
||||
},
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
const { form, usernameField } = await createTestRenderer({
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
setUsername,
|
||||
setUsername: ({ variables }) => {
|
||||
expectAndFail(variables).toEqual({
|
||||
username,
|
||||
});
|
||||
return {
|
||||
user: {
|
||||
id: "me",
|
||||
username,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { form, usernameField } = await createTestRenderer({
|
||||
resolvers,
|
||||
});
|
||||
const submitButton = form.find(
|
||||
i => i.type === "button" && i.props.type === "submit"
|
||||
);
|
||||
@@ -134,5 +134,5 @@ it("successfully sets username", async () => {
|
||||
await wait(() => expect(submitButton.props.disabled).toBe(false));
|
||||
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
expect(setUsername.called).toBe(true);
|
||||
expect(resolvers.Mutation!.setUsername!.called).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import sinon from "sinon";
|
||||
import {
|
||||
GQLResolver,
|
||||
QueryToModerationQueuesResolver,
|
||||
} from "talk-framework/schema";
|
||||
import {
|
||||
createAccessToken,
|
||||
createQueryResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
@@ -13,26 +20,41 @@ import {
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
moderationQueues: sinon.stub().returns(emptyModerationQueues),
|
||||
comments: sinon.stub().returns(emptyRejectedComments),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
},
|
||||
};
|
||||
const viewer = users.admins[0];
|
||||
|
||||
it("redirect when already logged in", async () => {
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/login");
|
||||
create({
|
||||
resolvers,
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
initLocalState: localRecord => {
|
||||
const { testRenderer, context } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
moderationQueues: createQueryResolverStub<
|
||||
QueryToModerationQueuesResolver
|
||||
>(() => emptyModerationQueues),
|
||||
comments: () => emptyRejectedComments,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
localRecord.setValue(createAccessToken(), "accessToken");
|
||||
localRecord.setValue("SIGN_IN", "authView");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context };
|
||||
}
|
||||
|
||||
it("redirect when already logged in", async () => {
|
||||
await createTestRenderer();
|
||||
await wait(() =>
|
||||
expect(window.location.toString()).toBe(
|
||||
"http://localhost/admin/moderate/reported"
|
||||
@@ -41,14 +63,8 @@ it("redirect when already logged in", async () => {
|
||||
});
|
||||
|
||||
it("redirect to redirectPath when already logged in", async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/login");
|
||||
create({
|
||||
resolvers,
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
await createTestRenderer({
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
localRecord.setValue(createAccessToken(), "accessToken");
|
||||
localRecord.setValue("/admin/moderate/pending", "redirectPath");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay";
|
||||
import { replaceHistoryLocation, wait } from "talk-framework/testHelpers";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { LOCAL_ID, lookup } from "talk-framework/lib/relay";
|
||||
import {
|
||||
GQLResolver,
|
||||
QueryToModerationQueuesResolver,
|
||||
} from "talk-framework/schema";
|
||||
import {
|
||||
createQueryResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import {
|
||||
@@ -12,39 +19,41 @@ import {
|
||||
settings,
|
||||
} from "../fixtures";
|
||||
|
||||
function createTestRenderer(): {
|
||||
testRenderer: ReactTestRenderer;
|
||||
context: TalkContext;
|
||||
} {
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
const resolvers = {
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
moderationQueues: sinon.stub().returns(emptyModerationQueues),
|
||||
comments: sinon.stub().returns(emptyRejectedComments),
|
||||
},
|
||||
};
|
||||
const { testRenderer, context } = create({
|
||||
resolvers,
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
moderationQueues: createQueryResolverStub<
|
||||
QueryToModerationQueuesResolver
|
||||
>(() => emptyModerationQueues),
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(false, "loggedIn");
|
||||
localRecord.setValue("SIGN_IN", "authView");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context };
|
||||
}
|
||||
|
||||
it("redirect when not logged in", async () => {
|
||||
const { context } = createTestRenderer();
|
||||
const { context } = await createTestRenderer();
|
||||
await wait(() => {
|
||||
expect(
|
||||
context.relayEnvironment
|
||||
.getStore()
|
||||
.getSource()
|
||||
.get(LOCAL_ID)!.redirectPath
|
||||
).toBe("/admin/moderate/reported");
|
||||
expect(lookup(context.relayEnvironment, LOCAL_ID)!.redirectPath).toBe(
|
||||
"/admin/moderate/reported"
|
||||
);
|
||||
expect(window.location.toString()).toBe("http://localhost/admin/login");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay";
|
||||
import { GQLUSER_ROLE } from "talk-framework/schema";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { LOCAL_ID, lookup } from "talk-framework/lib/relay";
|
||||
import { GQLResolver, GQLUSER_ROLE } from "talk-framework/schema";
|
||||
import {
|
||||
createAccessToken,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -20,29 +21,32 @@ import {
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
function createTestRenderer(
|
||||
userDiff: any = {}
|
||||
): {
|
||||
testRenderer: ReactTestRenderer;
|
||||
context: TalkContext;
|
||||
} {
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate/reported");
|
||||
const resolvers = {
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
moderationQueues: sinon.stub().returns(emptyModerationQueues),
|
||||
comments: sinon.stub().returns(emptyRejectedComments),
|
||||
viewer: sinon.stub().returns({ ...users.admins[0], ...userDiff }),
|
||||
},
|
||||
};
|
||||
const { testRenderer, context } = create({
|
||||
resolvers,
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
localRecord.setValue(createAccessToken(), "accessToken");
|
||||
localRecord.setValue("SIGN_IN", "authView");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context };
|
||||
@@ -51,7 +55,16 @@ function createTestRenderer(
|
||||
it("show restricted screen for commenters and staff", async () => {
|
||||
const restrictedRoles = [GQLUSER_ROLE.COMMENTER, GQLUSER_ROLE.STAFF];
|
||||
for (const role of restrictedRoles) {
|
||||
const { testRenderer } = createTestRenderer({ role });
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
role,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const authBox = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("authBox")
|
||||
);
|
||||
@@ -60,8 +73,15 @@ it("show restricted screen for commenters and staff", async () => {
|
||||
});
|
||||
|
||||
it("sign out when clicking on sign in as", async () => {
|
||||
const { context, testRenderer } = createTestRenderer({
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
const { testRenderer, context } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const authBox = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("authBox")
|
||||
@@ -81,18 +101,10 @@ it("sign out when clicking on sign in as", async () => {
|
||||
.props.onClick();
|
||||
|
||||
await wait(() => {
|
||||
expect(
|
||||
context.relayEnvironment
|
||||
.getStore()
|
||||
.getSource()
|
||||
.get(LOCAL_ID)!.redirectPath
|
||||
).toBe("/admin/moderate/reported");
|
||||
expect(lookup(context.relayEnvironment, LOCAL_ID)!.redirectPath).toBe(
|
||||
"/admin/moderate/reported"
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
context.relayEnvironment
|
||||
.getStore()
|
||||
.getSource()
|
||||
.get(LOCAL_ID)!.loggedIn
|
||||
).toBeFalsy();
|
||||
expect(lookup(context.relayEnvironment, LOCAL_ID)!.loggedIn).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { ReactTestInstance } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createAccessToken,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -16,32 +19,34 @@ import {
|
||||
settings,
|
||||
} from "../fixtures";
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
moderationQueues: sinon.stub().returns(emptyModerationQueues),
|
||||
comments: sinon.stub().returns(emptyRejectedComments),
|
||||
},
|
||||
};
|
||||
|
||||
const inputPredicate = (name: string) => (n: ReactTestInstance) => {
|
||||
return n.props.name === name && n.props.onChange;
|
||||
};
|
||||
|
||||
async function createTestRenderer() {
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
// deliberately setting to a different route,
|
||||
// it should be smart enough to reroute to /admin/login.
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
resolvers,
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(false, "loggedIn");
|
||||
localRecord.setValue("SIGN_IN", "authView");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const form = await waitForElement(() =>
|
||||
within(testRenderer.root).getByType("form")
|
||||
);
|
||||
@@ -121,11 +126,11 @@ it("shows server error", async () => {
|
||||
|
||||
it("submits form successfully", async () => {
|
||||
const { form, context } = await createTestRenderer();
|
||||
form
|
||||
.find(inputPredicate("email"))
|
||||
within(form)
|
||||
.getByLabelText("Email Address")
|
||||
.props.onChange({ target: { value: "hans@test.com" } });
|
||||
form
|
||||
.find(inputPredicate("password"))
|
||||
within(form)
|
||||
.getByLabelText("Password")
|
||||
.props.onChange({ target: { value: "testtest" } });
|
||||
|
||||
const accessToken = createAccessToken();
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { LOCAL_ID, lookup } from "talk-framework/lib/relay";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -16,26 +20,39 @@ import {
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
moderationQueues: sinon.stub().returns(emptyModerationQueues),
|
||||
comments: sinon.stub().returns(emptyRejectedComments),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
},
|
||||
};
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate/reported");
|
||||
const { testRenderer, context } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { testRenderer, context };
|
||||
}
|
||||
|
||||
it("logs out", async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
resolvers,
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
},
|
||||
});
|
||||
const { testRenderer, context } = await createTestRenderer();
|
||||
|
||||
const restMock = sinon.mock(context.rest);
|
||||
restMock
|
||||
@@ -60,11 +77,6 @@ it("logs out", async () => {
|
||||
signOutButton.props.onClick();
|
||||
|
||||
await wait(() => {
|
||||
expect(
|
||||
context.relayEnvironment
|
||||
.getStore()
|
||||
.getSource()
|
||||
.get(LOCAL_ID)!.loggedIn
|
||||
).toBeFalsy();
|
||||
expect(lookup(context.relayEnvironment, LOCAL_ID)!.loggedIn).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { merge } from "lodash";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import {
|
||||
GQLResolver,
|
||||
GQLUSER_ROLE,
|
||||
GQLUSER_STATUS,
|
||||
QueryToSettingsResolver,
|
||||
QueryToUsersResolver,
|
||||
QueryToViewerResolver,
|
||||
} from "talk-framework/schema";
|
||||
import {
|
||||
createMutationResolverStub,
|
||||
createQueryResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
findParentWithType,
|
||||
replaceHistoryLocation,
|
||||
waitForElement,
|
||||
@@ -18,11 +18,6 @@ import {
|
||||
within,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import {
|
||||
BanUserMutation,
|
||||
RemoveUserBanMutation,
|
||||
UpdateUserRoleMutation,
|
||||
} from "talk-admin/mutations";
|
||||
import create from "../create";
|
||||
import {
|
||||
communityUsers,
|
||||
@@ -35,29 +30,29 @@ beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/community");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
settings: createQueryResolverStub<QueryToSettingsResolver>(
|
||||
() => settings
|
||||
),
|
||||
users: createQueryResolverStub<QueryToUsersResolver>(variables => {
|
||||
expectAndFail(variables.role).toBeFalsy();
|
||||
return communityUsers;
|
||||
}),
|
||||
viewer: createQueryResolverStub<QueryToViewerResolver>(
|
||||
() => users.admins[0]
|
||||
),
|
||||
...resolver.Query,
|
||||
},
|
||||
};
|
||||
const createTestRenderer = async (
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) => {
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
users: ({ variables }) => {
|
||||
expectAndFail(variables.role).toBeFalsy();
|
||||
return communityUsers;
|
||||
},
|
||||
viewer: () => users.admins[0],
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const container = await waitForElement(() =>
|
||||
@@ -73,10 +68,12 @@ it("renders community", async () => {
|
||||
|
||||
it("renders empty community", async () => {
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
users: createQueryResolverStub<QueryToUsersResolver>(
|
||||
() => emptyCommunityUsers
|
||||
),
|
||||
resolvers: {
|
||||
Query: {
|
||||
users: createQueryResolverStub<QueryToUsersResolver>(
|
||||
() => emptyCommunityUsers
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(within(container).toJSON()).toMatchSnapshot();
|
||||
@@ -84,9 +81,9 @@ it("renders empty community", async () => {
|
||||
|
||||
it("filter by role", async () => {
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
users: createQueryResolverStub<QueryToUsersResolver>(
|
||||
(variables, callCount) => {
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
users: ({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
return communityUsers;
|
||||
@@ -94,9 +91,9 @@ it("filter by role", async () => {
|
||||
expectAndFail(variables.role).toBe(GQLUSER_ROLE.COMMENTER);
|
||||
return emptyCommunityUsers;
|
||||
}
|
||||
}
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const selectField = within(container).getByLabelText("Search by role");
|
||||
@@ -127,21 +124,24 @@ it("can't change viewer role", async () => {
|
||||
|
||||
it("change user role", async () => {
|
||||
const user = users.commenters[0];
|
||||
const updateUserRole = createMutationResolverStub<
|
||||
typeof UpdateUserRoleMutation
|
||||
>(variables => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
userID: user.id,
|
||||
role: GQLUSER_ROLE.STAFF,
|
||||
});
|
||||
const userRecord = merge({}, user, { role: variables.role });
|
||||
return {
|
||||
user: userRecord,
|
||||
};
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateUserRole: ({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
userID: user.id,
|
||||
role: GQLUSER_ROLE.STAFF,
|
||||
});
|
||||
const userRecord = pureMerge<typeof user>(user, {
|
||||
role: variables.role,
|
||||
});
|
||||
return {
|
||||
user: userRecord,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = await createTestRenderer({
|
||||
Mutation: { updateUserRole },
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const userRow = within(container).getByText(user.username!, {
|
||||
@@ -165,29 +165,34 @@ it("change user role", async () => {
|
||||
});
|
||||
|
||||
within(userRow).getByText("Staff");
|
||||
expect(updateUserRole.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateUserRole!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("can't change role as a moderator", async () => {
|
||||
const viewer = users.moderators[0];
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
viewer: createQueryResolverStub<QueryToViewerResolver>(() => viewer),
|
||||
},
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(() => within(container).getByLabelText("Change role")).toThrow();
|
||||
});
|
||||
|
||||
it("load more", async () => {
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
users: createQueryResolverStub<QueryToUsersResolver>(
|
||||
(variables, callCount) => {
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
users: ({ callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
return {
|
||||
edges: [
|
||||
{ node: users.admins[0], cursor: users.admins[0].createdAt },
|
||||
{
|
||||
node: users.admins[0],
|
||||
cursor: users.admins[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: users.commenters[0],
|
||||
cursor: users.commenters[0].createdAt,
|
||||
@@ -212,9 +217,9 @@ it("load more", async () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const loadMore = within(container).getByText("Load More");
|
||||
TestRenderer.act(() => {
|
||||
@@ -230,9 +235,9 @@ it("load more", async () => {
|
||||
|
||||
it("filter by search", async () => {
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
users: createQueryResolverStub<QueryToUsersResolver>(
|
||||
(variables, callCount) => {
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
users: ({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
return communityUsers;
|
||||
@@ -240,9 +245,9 @@ it("filter by search", async () => {
|
||||
expectAndFail(variables.query).toBe("search");
|
||||
return emptyCommunityUsers;
|
||||
}
|
||||
}
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const searchField = within(container).getByLabelText("Search by username", {
|
||||
@@ -264,9 +269,9 @@ it("filter by search", async () => {
|
||||
|
||||
it("filter by status", async () => {
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
users: createQueryResolverStub<QueryToUsersResolver>(
|
||||
(variables, callCount) => {
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
users: ({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
return communityUsers;
|
||||
@@ -274,9 +279,9 @@ it("filter by status", async () => {
|
||||
expectAndFail(variables.status).toBe("BANNED");
|
||||
return emptyCommunityUsers;
|
||||
}
|
||||
}
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const statusField = within(container).getByLabelText(
|
||||
@@ -314,25 +319,28 @@ it("can't change staff, moderator and admin status", async () => {
|
||||
|
||||
it("ban user", async () => {
|
||||
const user = users.commenters[0];
|
||||
const banUser = createMutationResolverStub<typeof BanUserMutation>(
|
||||
variables => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
userID: user.id,
|
||||
});
|
||||
const userRecord = merge({}, user, {
|
||||
status: {
|
||||
current: user.status.current.concat(GQLUSER_STATUS.BANNED),
|
||||
banned: { active: true },
|
||||
},
|
||||
});
|
||||
return {
|
||||
user: userRecord,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
banUser: ({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
userID: user.id,
|
||||
});
|
||||
const userRecord = pureMerge<typeof user>(user, {
|
||||
status: {
|
||||
current: user.status.current.concat(GQLUSER_STATUS.BANNED),
|
||||
ban: { active: true },
|
||||
},
|
||||
});
|
||||
return {
|
||||
user: userRecord,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { container, testRenderer } = await createTestRenderer({
|
||||
Mutation: { banUser },
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const userRow = within(container).getByText(user.username!, {
|
||||
@@ -368,32 +376,32 @@ it("ban user", async () => {
|
||||
.getByText("Ban User")
|
||||
.props.onClick();
|
||||
within(userRow).getByText("Banned");
|
||||
expect(banUser.called).toBe(true);
|
||||
expect(resolvers.Mutation!.banUser!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("remove user ban", async () => {
|
||||
const user = users.bannedCommenter;
|
||||
const removeUserBan = createMutationResolverStub<
|
||||
typeof RemoveUserBanMutation
|
||||
>(variables => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
userID: user.id,
|
||||
});
|
||||
const userRecord = merge({}, user, {
|
||||
status: {
|
||||
current: user.status.current.filter(s => s !== GQLUSER_STATUS.BANNED),
|
||||
banned: { active: false },
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
removeUserBan: ({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
userID: user.id,
|
||||
});
|
||||
const userRecord = pureMerge<typeof user>(user, {
|
||||
status: {
|
||||
current: user.status.current.filter(
|
||||
s => s !== GQLUSER_STATUS.BANNED
|
||||
),
|
||||
ban: { active: false },
|
||||
},
|
||||
});
|
||||
return {
|
||||
user: userRecord,
|
||||
};
|
||||
},
|
||||
});
|
||||
return {
|
||||
user: userRecord,
|
||||
};
|
||||
});
|
||||
|
||||
const { container } = await createTestRenderer({
|
||||
Mutation: { removeUserBan },
|
||||
},
|
||||
Query: {
|
||||
users: createQueryResolverStub<QueryToUsersResolver>(() => ({
|
||||
users: () => ({
|
||||
edges: [
|
||||
{
|
||||
node: user,
|
||||
@@ -401,10 +409,14 @@ it("remove user ban", async () => {
|
||||
},
|
||||
],
|
||||
pageInfo: { endCursor: null, hasNextPage: false },
|
||||
})),
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = await createTestRenderer({
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const userRow = within(container).getByText(user.username!, {
|
||||
selector: "tr",
|
||||
});
|
||||
@@ -426,5 +438,5 @@ it("remove user ban", async () => {
|
||||
});
|
||||
|
||||
within(userRow).getByText("Active");
|
||||
expect(removeUserBan.called).toBe(true);
|
||||
expect(resolvers.Mutation!.removeUserBan!.called).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createSinonStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -16,25 +16,30 @@ beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/advanced");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(resolver, "Query.settings"))),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
},
|
||||
};
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const configureContainer = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("configure-container")
|
||||
);
|
||||
@@ -45,12 +50,13 @@ const createTestRenderer = async (resolver: any = {}) => {
|
||||
"configure-sideBar-saveChanges"
|
||||
);
|
||||
return {
|
||||
context,
|
||||
testRenderer,
|
||||
configureContainer,
|
||||
advancedContainer,
|
||||
saveChangesButton,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
it("renders configure advanced", async () => {
|
||||
const { configureContainer } = await createTestRenderer();
|
||||
@@ -58,25 +64,22 @@ it("renders configure advanced", async () => {
|
||||
});
|
||||
|
||||
it("change custom css", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.customCSSURL).toEqual("./custom.css");
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.customCSSURL).toEqual("./custom.css");
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
advancedContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const customCSSField = within(advancedContainer).getByLabelText("Custom CSS");
|
||||
@@ -99,29 +102,26 @@ it("change custom css", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("change permitted domains to be empty", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.domains).toEqual([]);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.domains).toEqual([]);
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
advancedContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const permittedDomainsField = within(advancedContainer).getByLabelText(
|
||||
@@ -146,32 +146,29 @@ it("change permitted domains to be empty", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("change permitted domains to include more domains", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.domains).toEqual([
|
||||
"localhost:8080",
|
||||
"localhost:3000",
|
||||
]);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.domains).toEqual([
|
||||
"localhost:8080",
|
||||
"localhost:3000",
|
||||
]);
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
advancedContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const permittedDomainsField = within(advancedContainer).getByLabelText(
|
||||
@@ -196,5 +193,5 @@ it("change permitted domains to include more domains", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { ReactTestInstance } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { timeout } from "talk-common/utils";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createSinonStub,
|
||||
inputPredicate,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
limitSnapshotTo,
|
||||
replaceHistoryLocation,
|
||||
waitForElement,
|
||||
@@ -14,29 +17,44 @@ import {
|
||||
import create from "../create";
|
||||
import { settingsWithEmptyAuth, users } from "../fixtures";
|
||||
|
||||
/**
|
||||
* This is depreacted, do not use it anymore.
|
||||
* @deprecated
|
||||
*/
|
||||
const deprecatedInputPredicate = (nameOrID: string) => (
|
||||
n: ReactTestInstance
|
||||
) => {
|
||||
return (
|
||||
[n.props.name, n.props.id].indexOf(nameOrID) > -1 &&
|
||||
["input", "button"].indexOf(n.type as string) > -1
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/auth");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(
|
||||
merge({}, settingsWithEmptyAuth, get(resolver, "Query.settings"))
|
||||
),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
},
|
||||
};
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settingsWithEmptyAuth,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const configureContainer = await waitForElement(() =>
|
||||
@@ -45,8 +63,8 @@ const createTestRenderer = async (resolver: any = {}) => {
|
||||
const authContainer = await waitForElement(() =>
|
||||
within(configureContainer).getByTestID("configure-authContainer")
|
||||
);
|
||||
return { testRenderer, configureContainer, authContainer };
|
||||
};
|
||||
return { context, testRenderer, configureContainer, authContainer };
|
||||
}
|
||||
|
||||
it("renders configure auth", async () => {
|
||||
const { configureContainer } = await createTestRenderer();
|
||||
@@ -55,32 +73,34 @@ it("renders configure auth", async () => {
|
||||
|
||||
it("regenerate sso key", async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
Mutation: {
|
||||
regenerateSSOKey: createSinonStub(s =>
|
||||
s.callsFake((_: any, data: any) => {
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
regenerateSSOKey: () => {
|
||||
return {
|
||||
settings: merge({}, settingsWithEmptyAuth, {
|
||||
auth: {
|
||||
integrations: {
|
||||
sso: {
|
||||
key: "==GENERATED_KEY==",
|
||||
keyGeneratedAt: "2018-11-12T23:26:06.239Z",
|
||||
settings: pureMerge<typeof settingsWithEmptyAuth>(
|
||||
settingsWithEmptyAuth,
|
||||
{
|
||||
auth: {
|
||||
integrations: {
|
||||
sso: {
|
||||
key: "==GENERATED_KEY==",
|
||||
keyGeneratedAt: "2018-11-12T23:26:06.239Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
}
|
||||
),
|
||||
};
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.sso.enabled"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.sso.enabled"))
|
||||
.props.onChange({});
|
||||
|
||||
testRenderer.root
|
||||
.find(inputPredicate("configure-auth-sso-regenerate"))
|
||||
.find(deprecatedInputPredicate("configure-auth-sso-regenerate"))
|
||||
.props.onClick();
|
||||
|
||||
await timeout();
|
||||
@@ -95,7 +115,7 @@ it("prevents admin lock out", async () => {
|
||||
|
||||
// Let's disable local auth.
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.local.enabled"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.local.enabled"))
|
||||
.props.onChange();
|
||||
|
||||
// Send form
|
||||
@@ -109,10 +129,10 @@ it("prevents admin lock out", async () => {
|
||||
it("prevents stream lock out", async () => {
|
||||
let settingsRecord = cloneDeep(settingsWithEmptyAuth);
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: createSinonStub(s =>
|
||||
s.callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.auth.integrations.local).toEqual({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.auth!.integrations!.local).toEqual({
|
||||
enabled: true,
|
||||
allowRegistration: true,
|
||||
targetFilter: {
|
||||
@@ -120,14 +140,13 @@ it("prevents stream lock out", async () => {
|
||||
stream: false,
|
||||
},
|
||||
});
|
||||
settingsRecord = merge({}, settingsRecord, data.input.settings);
|
||||
settingsRecord = pureMerge(settingsRecord, variables.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const origConfirm = window.confirm;
|
||||
const stubContinue = sinon.stub().returns(true);
|
||||
@@ -137,7 +156,9 @@ it("prevents stream lock out", async () => {
|
||||
window.confirm = stubCancel;
|
||||
// Let's disable stream target in local auth.
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.local.targetFilter.stream"))
|
||||
.find(
|
||||
deprecatedInputPredicate("auth.integrations.local.targetFilter.stream")
|
||||
)
|
||||
.props.onChange();
|
||||
|
||||
// Send form
|
||||
@@ -154,7 +175,9 @@ it("prevents stream lock out", async () => {
|
||||
window.confirm = stubContinue;
|
||||
// Let's enable stream target in local auth.
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.local.targetFilter.stream"))
|
||||
.find(
|
||||
deprecatedInputPredicate("auth.integrations.local.targetFilter.stream")
|
||||
)
|
||||
.props.onChange();
|
||||
|
||||
// Send form
|
||||
@@ -167,83 +190,74 @@ it("prevents stream lock out", async () => {
|
||||
});
|
||||
|
||||
it("change settings", async () => {
|
||||
let settingsRecord = cloneDeep(settingsWithEmptyAuth);
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
Query: {
|
||||
discoverOIDCConfiguration: createSinonStub(s =>
|
||||
s.callsFake((_: any, data: any) => {
|
||||
expectAndFail(data).toEqual({ issuer: "http://issuer.com" });
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
discoverOIDCConfiguration: ({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ issuer: "http://issuer.com" });
|
||||
return {
|
||||
issuer: "http://issuer.com",
|
||||
tokenURL: "http://issuer.com/tokenURL",
|
||||
jwksURI: "http://issuer.com/jwksURI",
|
||||
authorizationURL: "http://issuer.com/authorizationURL",
|
||||
};
|
||||
})
|
||||
),
|
||||
},
|
||||
Mutation: {
|
||||
updateSettings: createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(
|
||||
data.input.settings.auth.integrations.facebook
|
||||
).toEqual({
|
||||
enabled: true,
|
||||
allowRegistration: true,
|
||||
targetFilter: {
|
||||
admin: true,
|
||||
stream: true,
|
||||
},
|
||||
clientID: "myClientID",
|
||||
clientSecret: "myClientSecret",
|
||||
});
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.auth.integrations.oidc).toEqual({
|
||||
enabled: true,
|
||||
allowRegistration: false,
|
||||
targetFilter: {
|
||||
admin: true,
|
||||
stream: true,
|
||||
},
|
||||
name: "name",
|
||||
clientID: "clientID",
|
||||
clientSecret: "clientSecret",
|
||||
issuer: "http://issuer.com",
|
||||
jwksURI: "http://issuer.com/jwksURI",
|
||||
authorizationURL: "http://issuer.com/authorizationURL",
|
||||
tokenURL: "http://issuer.com/tokenURL",
|
||||
});
|
||||
(settingsRecord.auth.integrations.oidc as any) = merge(
|
||||
settingsRecord.auth.integrations.oidc,
|
||||
data.input.configuration
|
||||
);
|
||||
return {
|
||||
integration: settingsRecord.auth.integrations.oidc,
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
updateSettings: ({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
expectAndFail(
|
||||
variables.settings.auth!.integrations!.facebook
|
||||
).toEqual({
|
||||
enabled: true,
|
||||
allowRegistration: true,
|
||||
targetFilter: {
|
||||
admin: true,
|
||||
stream: true,
|
||||
},
|
||||
clientID: "myClientID",
|
||||
clientSecret: "myClientSecret",
|
||||
});
|
||||
return {
|
||||
settings: pureMerge(settingsWithEmptyAuth, variables.settings),
|
||||
};
|
||||
default:
|
||||
expectAndFail(
|
||||
variables.settings.auth!.integrations!.oidc
|
||||
).toEqual({
|
||||
enabled: true,
|
||||
allowRegistration: false,
|
||||
targetFilter: {
|
||||
admin: true,
|
||||
stream: true,
|
||||
},
|
||||
name: "name",
|
||||
clientID: "clientID",
|
||||
clientSecret: "clientSecret",
|
||||
issuer: "http://issuer.com",
|
||||
jwksURI: "http://issuer.com/jwksURI",
|
||||
authorizationURL: "http://issuer.com/authorizationURL",
|
||||
tokenURL: "http://issuer.com/tokenURL",
|
||||
});
|
||||
return {
|
||||
settings: pureMerge(settingsWithEmptyAuth, variables.settings),
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Let's change some facebook settings.
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.facebook.enabled"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.facebook.enabled"))
|
||||
.props.onChange({});
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.facebook.clientID"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.facebook.clientID"))
|
||||
.props.onChange("myClientID");
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.facebook.clientSecret"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.facebook.clientSecret"))
|
||||
.props.onChange("myClientSecret");
|
||||
expect(
|
||||
limitSnapshotTo("configure-auth-facebook-container", testRenderer.toJSON())
|
||||
@@ -262,18 +276,20 @@ it("change settings", async () => {
|
||||
// Disable other fields while submitting
|
||||
// We are only testing for one here right now..
|
||||
expect(
|
||||
testRenderer.root.find(inputPredicate("auth.integrations.facebook.enabled"))
|
||||
.props.disabled
|
||||
testRenderer.root.find(
|
||||
deprecatedInputPredicate("auth.integrations.facebook.enabled")
|
||||
).props.disabled
|
||||
).toBe(true);
|
||||
await timeout();
|
||||
expect(
|
||||
testRenderer.root.find(inputPredicate("auth.integrations.facebook.enabled"))
|
||||
.props.disabled
|
||||
testRenderer.root.find(
|
||||
deprecatedInputPredicate("auth.integrations.facebook.enabled")
|
||||
).props.disabled
|
||||
).toBe(false);
|
||||
|
||||
// Now let's enable oidc
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.oidc.enabled"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.oidc.enabled"))
|
||||
.props.onChange({});
|
||||
|
||||
expect(
|
||||
@@ -288,21 +304,21 @@ it("change settings", async () => {
|
||||
|
||||
// Fill form
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.oidc.name"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.oidc.name"))
|
||||
.props.onChange("name");
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.oidc.clientID"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.oidc.clientID"))
|
||||
.props.onChange("clientID");
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.oidc.clientSecret"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.oidc.clientSecret"))
|
||||
.props.onChange("clientSecret");
|
||||
testRenderer.root
|
||||
.find(inputPredicate("auth.integrations.oidc.issuer"))
|
||||
.find(deprecatedInputPredicate("auth.integrations.oidc.issuer"))
|
||||
.props.onChange("http://issuer.com");
|
||||
|
||||
// Discover the rest.
|
||||
testRenderer.root
|
||||
.find(inputPredicate("configure-auth-oidc-discover"))
|
||||
.find(deprecatedInputPredicate("configure-auth-oidc-discover"))
|
||||
.props.onClick();
|
||||
await timeout();
|
||||
|
||||
@@ -315,12 +331,14 @@ it("change settings", async () => {
|
||||
// Disable other fields while submitting
|
||||
// We are only testing for one here right now..
|
||||
expect(
|
||||
testRenderer.root.find(inputPredicate("auth.integrations.oidc.enabled"))
|
||||
.props.disabled
|
||||
testRenderer.root.find(
|
||||
deprecatedInputPredicate("auth.integrations.oidc.enabled")
|
||||
).props.disabled
|
||||
).toBe(true);
|
||||
await timeout();
|
||||
expect(
|
||||
testRenderer.root.find(inputPredicate("auth.integrations.oidc.enabled"))
|
||||
.props.disabled
|
||||
testRenderer.root.find(
|
||||
deprecatedInputPredicate("auth.integrations.oidc.enabled")
|
||||
).props.disabled
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { ERROR_CODES } from "talk-common/errors";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { InvalidRequestError } from "talk-framework/lib/errors";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createSinonStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -18,27 +18,27 @@ beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/general");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (
|
||||
resolver: any = {},
|
||||
options: { muteNetworkErrors?: boolean } = {}
|
||||
) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(resolver, "Query.settings"))),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
},
|
||||
};
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const configureContainer = await waitForElement(() =>
|
||||
@@ -51,12 +51,13 @@ const createTestRenderer = async (
|
||||
"configure-sideBar-saveChanges"
|
||||
);
|
||||
return {
|
||||
context,
|
||||
testRenderer,
|
||||
configureContainer,
|
||||
generalContainer,
|
||||
saveChangesButton,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
it("renders configure general", async () => {
|
||||
const { configureContainer } = await createTestRenderer();
|
||||
@@ -64,28 +65,25 @@ it("renders configure general", async () => {
|
||||
});
|
||||
|
||||
it("change site wide commenting", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.disableCommenting).toEqual({
|
||||
enabled: true,
|
||||
message: "Closing message",
|
||||
});
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.disableCommenting).toEqual({
|
||||
enabled: true,
|
||||
message: "Closing message",
|
||||
});
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
generalContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const sitewideCommentingContainer = within(generalContainer).getAllByText(
|
||||
@@ -121,34 +119,32 @@ it("change site wide commenting", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("change community guidlines", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.communityGuidelines.content).toEqual(
|
||||
"This is the community guidlines summary"
|
||||
);
|
||||
expectAndFail(data.input.settings.communityGuidelines.enabled).toEqual(
|
||||
true
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.communityGuidelines!.content).toEqual(
|
||||
"This is the community guidlines summary"
|
||||
);
|
||||
expectAndFail(variables.settings.communityGuidelines!.enabled).toEqual(
|
||||
true
|
||||
);
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
configureContainer,
|
||||
generalContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const guidelinesContainer = within(generalContainer).getAllByText(
|
||||
@@ -182,32 +178,27 @@ it("change community guidlines", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("change closed stream message", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.closeCommenting.message).toEqual(
|
||||
"The stream has been closed"
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.closeCommenting!.message).toEqual(
|
||||
"The stream has been closed"
|
||||
);
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
generalContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
});
|
||||
} = await createTestRenderer({ resolvers });
|
||||
|
||||
const contentField = within(generalContainer).getByLabelText(
|
||||
"Closed Stream Message"
|
||||
@@ -226,33 +217,28 @@ it("change closed stream message", async () => {
|
||||
|
||||
// Wait for submission to be finished
|
||||
await wait(() => {
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("change comment editing time", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.editCommentWindowLength).toEqual(
|
||||
108000
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.editCommentWindowLength).toEqual(
|
||||
108000
|
||||
);
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
generalContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
});
|
||||
} = await createTestRenderer({ resolvers });
|
||||
|
||||
const durationFieldset = within(generalContainer).getByText(
|
||||
"Comment Edit Timeframe",
|
||||
@@ -296,34 +282,31 @@ it("change comment editing time", async () => {
|
||||
|
||||
// Wait for submission to be finished
|
||||
await wait(() => {
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("change comment length limitations", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.charCount).toEqual({
|
||||
enabled: true,
|
||||
min: null,
|
||||
max: 3000,
|
||||
});
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.charCount).toEqual({
|
||||
enabled: true,
|
||||
min: null,
|
||||
max: 3000,
|
||||
});
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
generalContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const commentLengthContainer = within(generalContainer).getByText(
|
||||
@@ -389,33 +372,28 @@ it("change comment length limitations", async () => {
|
||||
expect(minField.props.disabled).toBe(false);
|
||||
expect(maxField.props.disabled).toBe(false);
|
||||
});
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("change closing comment streams", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.closeCommenting.auto).toEqual(true);
|
||||
expectAndFail(data.input.settings.closeCommenting.timeout).toEqual(
|
||||
2592000
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.closeCommenting!.auto).toEqual(true);
|
||||
expectAndFail(variables.settings.closeCommenting!.timeout).toEqual(
|
||||
2592000
|
||||
);
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
generalContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
});
|
||||
} = await createTestRenderer({ resolvers });
|
||||
|
||||
const closingCommentStreamsContainer = within(generalContainer).getByText(
|
||||
"Closing Comment Streams",
|
||||
@@ -464,23 +442,22 @@ it("change closing comment streams", async () => {
|
||||
expect(valueField.props.disabled).toBe(false);
|
||||
expect(unitField.props.disabled).toBe(false);
|
||||
});
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("handle server error", async () => {
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
throw new InvalidRequestError({ code: ERROR_CODES.INTERNAL_ERROR });
|
||||
})
|
||||
);
|
||||
const { configureContainer, generalContainer } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: () => {
|
||||
throw new InvalidRequestError({ code: ERROR_CODES.INTERNAL_ERROR });
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
});
|
||||
|
||||
const { configureContainer, generalContainer } = await createTestRenderer({
|
||||
resolvers,
|
||||
muteNetworkErrors: true,
|
||||
});
|
||||
|
||||
const contentField = within(generalContainer).getByLabelText(
|
||||
"Closed Stream Message"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createSinonStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -16,23 +16,27 @@ beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/moderation");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(resolver, "Query.settings"))),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
},
|
||||
};
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const configureContainer = await waitForElement(() =>
|
||||
@@ -50,7 +54,7 @@ const createTestRenderer = async (resolver: any = {}) => {
|
||||
moderationContainer,
|
||||
saveChangesButton,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
it("renders configure moderation", async () => {
|
||||
const { configureContainer } = await createTestRenderer();
|
||||
@@ -58,30 +62,25 @@ it("renders configure moderation", async () => {
|
||||
});
|
||||
|
||||
it("change akismet settings", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.integrations.akismet).toEqual({
|
||||
enabled: true,
|
||||
key: "my api key",
|
||||
site: "https://coralproject.net",
|
||||
});
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.integrations!.akismet).toEqual({
|
||||
enabled: true,
|
||||
key: "my api key",
|
||||
site: "https://coralproject.net",
|
||||
});
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
moderationContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
});
|
||||
} = await createTestRenderer({ resolvers });
|
||||
|
||||
const akismetContainer = within(moderationContainer).getByText(
|
||||
"Akismet Spam Detection Filter",
|
||||
@@ -137,48 +136,41 @@ it("change akismet settings", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("change perspective settings", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.integrations.perspective).toEqual({
|
||||
doNotStore: false,
|
||||
enabled: true,
|
||||
endpoint: "https://custom-endpoint.net",
|
||||
key: "my api key",
|
||||
threshold: 0.1,
|
||||
});
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
expectAndFail(variables.settings.integrations!.perspective).toEqual(
|
||||
{
|
||||
doNotStore: false,
|
||||
enabled: true,
|
||||
endpoint: "https://custom-endpoint.net",
|
||||
key: "my api key",
|
||||
threshold: 0.1,
|
||||
}
|
||||
);
|
||||
break;
|
||||
default:
|
||||
expectAndFail(
|
||||
variables.settings.integrations!.perspective!.threshold
|
||||
).toBeNull();
|
||||
}
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(
|
||||
data.input.settings.integrations.perspective.threshold
|
||||
).toBeNull();
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
moderationContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
});
|
||||
} = await createTestRenderer({ resolvers });
|
||||
|
||||
const perspectiveContainer = within(moderationContainer).getByText(
|
||||
"Perspective Toxic Comment Filter",
|
||||
@@ -258,7 +250,7 @@ it("change perspective settings", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.calledOnce).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.calledOnce).toBe(true);
|
||||
|
||||
// Use default threshold.
|
||||
thresholdField.props.onChange("");
|
||||
@@ -278,5 +270,5 @@ it("change perspective settings", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.calledTwice).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.calledTwice).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createSinonStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
@@ -16,23 +16,27 @@ beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/organization");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(resolver, "Query.settings"))),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
},
|
||||
};
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const configureContainer = await waitForElement(() =>
|
||||
@@ -50,7 +54,7 @@ const createTestRenderer = async (resolver: any = {}) => {
|
||||
organizationContainer,
|
||||
saveChangesButton,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
it("renders configure organization", async () => {
|
||||
const { configureContainer } = await createTestRenderer();
|
||||
@@ -58,28 +62,23 @@ it("renders configure organization", async () => {
|
||||
});
|
||||
|
||||
it("change organization name", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.organization.name).toEqual(
|
||||
"Coral Test"
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.organization!.name).toEqual(
|
||||
"Coral Test"
|
||||
);
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
organizationContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
});
|
||||
} = await createTestRenderer({ resolvers });
|
||||
|
||||
const organizationNameField = within(organizationContainer).getByLabelText(
|
||||
"Organization Name"
|
||||
@@ -119,32 +118,27 @@ it("change organization name", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("change organization contact email", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.organization.contactEmail).toEqual(
|
||||
"test@coralproject.net"
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.organization!.contactEmail).toEqual(
|
||||
"test@coralproject.net"
|
||||
);
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
organizationContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
});
|
||||
} = await createTestRenderer({ resolvers });
|
||||
|
||||
const organizationEmailField = within(organizationContainer).getByLabelText(
|
||||
"Organization Email"
|
||||
@@ -184,5 +178,5 @@ it("change organization contact email", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { GQLUSER_ROLE } from "talk-framework/schema";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver, GQLUSER_ROLE } from "talk-framework/schema";
|
||||
import {
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
waitForElement,
|
||||
within,
|
||||
@@ -15,40 +15,43 @@ beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/general");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (
|
||||
resolver: any = {},
|
||||
options: { muteNetworkErrors?: boolean } = {}
|
||||
) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(resolver, "Query.settings"))),
|
||||
},
|
||||
};
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return {
|
||||
testRenderer,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
it("denies access to moderators", async () => {
|
||||
const deniedRoles = [GQLUSER_ROLE.MODERATOR];
|
||||
for (const r of deniedRoles) {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
Query: {
|
||||
viewer: sinon.stub().returns({ ...users.admins[0], role: r }),
|
||||
},
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () => ({ ...viewer, role: r }),
|
||||
},
|
||||
}),
|
||||
});
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByText("Sign in with a different account")
|
||||
@@ -60,9 +63,11 @@ it("allows access to admins", async () => {
|
||||
const deniedRoles = [GQLUSER_ROLE.ADMIN];
|
||||
for (const r of deniedRoles) {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
Query: {
|
||||
viewer: sinon.stub().returns({ ...users.admins[0], role: r }),
|
||||
},
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () => ({ ...viewer, role: r }),
|
||||
},
|
||||
}),
|
||||
});
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("configure-container")
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import {
|
||||
createSinonStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import create from "../create";
|
||||
import { settings, users } from "../fixtures";
|
||||
|
||||
@@ -16,23 +16,27 @@ beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/wordList");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(resolver, "Query.settings"))),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
},
|
||||
};
|
||||
const viewer = users.admins[0];
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const configureContainer = await waitForElement(() =>
|
||||
@@ -50,7 +54,7 @@ const createTestRenderer = async (resolver: any = {}) => {
|
||||
wordListContainer,
|
||||
saveChangesButton,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
it("renders configure wordList", async () => {
|
||||
const { configureContainer } = await createTestRenderer();
|
||||
@@ -58,28 +62,25 @@ it("renders configure wordList", async () => {
|
||||
});
|
||||
|
||||
it("change banned and suspect words", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.wordList).toEqual({
|
||||
banned: ["Fuck", "Asshole"],
|
||||
suspect: ["idiot", "shame"],
|
||||
});
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const resolvers = createResolversStub<GQLResolver>({
|
||||
Mutation: {
|
||||
updateSettings: ({ variables }) => {
|
||||
expectAndFail(variables.settings.wordList).toEqual({
|
||||
banned: ["Fuck", "Asshole"],
|
||||
suspect: ["idiot", "shame"],
|
||||
});
|
||||
return {
|
||||
settings: pureMerge(settings, variables.settings),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
configureContainer,
|
||||
wordListContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const bannedField = within(wordListContainer).getByLabelText(
|
||||
@@ -110,5 +111,5 @@ it("change banned and suspect words", async () => {
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
expect(resolvers.Mutation!.updateSettings!.called).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,67 +1,12 @@
|
||||
import { EventEmitter2 } from "eventemitter2";
|
||||
import { IResolvers } from "graphql-tools";
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
|
||||
|
||||
import EntryContainer from "talk-admin/containers/EntryContainer";
|
||||
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
|
||||
import { PostMessageService } from "talk-framework/lib/postMessage";
|
||||
import { RestClient } from "talk-framework/lib/rest";
|
||||
import { createPromisifiedStorage } from "talk-framework/lib/storage";
|
||||
import { createUUIDGenerator } from "talk-framework/testHelpers";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createTestRenderer,
|
||||
CreateTestRendererParams,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
|
||||
interface CreateParams {
|
||||
logNetwork?: boolean;
|
||||
resolvers?: IResolvers<any, any>;
|
||||
muteNetworkErrors?: boolean;
|
||||
initLocalState?: (
|
||||
local: RecordProxy,
|
||||
source: RecordSourceProxy,
|
||||
environment: Environment
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default function create(params: CreateParams) {
|
||||
const environment = createEnvironment({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: params.logNetwork,
|
||||
resolvers: params.resolvers,
|
||||
muteNetworkErrors: params.muteNetworkErrors,
|
||||
initLocalState: (localRecord, source, env) => {
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, env);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
locales: ["en-US"],
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createPromisifiedStorage(),
|
||||
sessionStorage: createPromisifiedStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
browserInfo: { ios: false },
|
||||
uuidGenerator: createUUIDGenerator(),
|
||||
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
|
||||
clearSession: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
TestRenderer.act(() => {
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<EntryContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
});
|
||||
|
||||
return { context, testRenderer: testRenderer! };
|
||||
export default function create(params: CreateTestRendererParams<GQLResolver>) {
|
||||
return createTestRenderer<GQLResolver>("admin", <EntryContainer />, params);
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { IResolvers } from "graphql-tools";
|
||||
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
|
||||
import { createRelayEnvironment } from "talk-framework/testHelpers";
|
||||
|
||||
interface CreateEnvironmentParams {
|
||||
logNetwork?: boolean;
|
||||
resolvers?: IResolvers<any, any>;
|
||||
muteNetworkErrors?: boolean;
|
||||
initLocalState?: (
|
||||
local: RecordProxy,
|
||||
source: RecordSourceProxy,
|
||||
environment: Environment
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default function createEnvironment(params: CreateEnvironmentParams) {
|
||||
return createRelayEnvironment({
|
||||
network: {
|
||||
logNetwork: params.logNetwork,
|
||||
muteNetworkErrors: params.muteNetworkErrors,
|
||||
resolvers: params.resolvers || {},
|
||||
projectName: "tenant",
|
||||
},
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import path from "path";
|
||||
|
||||
import { createFluentBundle } from "talk-framework/testHelpers";
|
||||
|
||||
export default function create() {
|
||||
return createFluentBundle(
|
||||
"admin",
|
||||
path.resolve(__dirname, "../../../../locales/en-US")
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { noop } from "lodash";
|
||||
import { ReactElement } from "react";
|
||||
|
||||
export default function createNodeMock(element: ReactElement<any>) {
|
||||
if (element.type === "div") {
|
||||
return {
|
||||
innerHtml: "",
|
||||
className: "",
|
||||
focus: noop,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
import { get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import {
|
||||
createSinonStub,
|
||||
GQLResolver,
|
||||
UserToCommentModerationActionHistoryResolver,
|
||||
} from "talk-framework/schema";
|
||||
import {
|
||||
createQueryResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
toJSON,
|
||||
waitForElement,
|
||||
@@ -17,71 +21,73 @@ beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/auth");
|
||||
});
|
||||
|
||||
const commentModerationActionHistory = createSinonStub(
|
||||
s => s.throws(),
|
||||
s =>
|
||||
s.withArgs({ first: 5 }).returns({
|
||||
edges: [
|
||||
{
|
||||
node: moderationActions[0],
|
||||
cursor: moderationActions[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: moderationActions[1],
|
||||
cursor: moderationActions[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: moderationActions[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
}),
|
||||
s =>
|
||||
s
|
||||
.withArgs({
|
||||
first: 10,
|
||||
after: moderationActions[1].createdAt,
|
||||
})
|
||||
.returns({
|
||||
edges: [
|
||||
{
|
||||
node: moderationActions[2],
|
||||
cursor: moderationActions[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: moderationActions[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
const viewer = users.admins[0];
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
viewer: sinon.stub().returns({
|
||||
...users.admins[0],
|
||||
commentModerationActionHistory,
|
||||
}),
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(resolver, "Query.settings"))),
|
||||
},
|
||||
};
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () =>
|
||||
pureMerge(viewer, {
|
||||
commentModerationActionHistory: createQueryResolverStub<
|
||||
UserToCommentModerationActionHistoryResolver
|
||||
>(({ variables }) => {
|
||||
if (variables.first === 5) {
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: moderationActions[0],
|
||||
cursor: moderationActions[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: moderationActions[1],
|
||||
cursor: moderationActions[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: moderationActions[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 10,
|
||||
after: moderationActions[1].createdAt,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: moderationActions[2],
|
||||
cursor: moderationActions[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: moderationActions[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
await waitForElement(() => getByTestID("decisionHistory-toggle"));
|
||||
return testRenderer;
|
||||
};
|
||||
}
|
||||
|
||||
async function createTestRendererAndOpenPopover() {
|
||||
const testRenderer = await createTestRenderer();
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import {
|
||||
createSinonStub,
|
||||
GQLCOMMENT_STATUS,
|
||||
GQLResolver,
|
||||
ModerationQueueToCommentsResolver,
|
||||
MutationToAcceptCommentResolver,
|
||||
MutationToRejectCommentResolver,
|
||||
QueryToCommentResolver,
|
||||
} from "talk-framework/schema";
|
||||
import {
|
||||
createMutationResolverStub,
|
||||
createQueryResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
toJSON,
|
||||
waitForElement,
|
||||
@@ -20,43 +29,37 @@ import {
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/moderate");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(resolver, "Query.settings"))),
|
||||
moderationQueues: sinon
|
||||
.stub()
|
||||
.returns(
|
||||
merge(
|
||||
{},
|
||||
emptyModerationQueues,
|
||||
get(resolver, "Query.moderationQueues")
|
||||
)
|
||||
),
|
||||
comments:
|
||||
get(resolver, "Query.comments") ||
|
||||
sinon.stub().returns(emptyRejectedComments),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
},
|
||||
};
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
comments: () => emptyRejectedComments,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return testRenderer;
|
||||
};
|
||||
}
|
||||
|
||||
describe("navigation bar", () => {
|
||||
it("renders navigation bar (empty queues)", async () => {
|
||||
@@ -78,48 +81,16 @@ describe("reported queue", () => {
|
||||
|
||||
it("renders reported queue with comments", async () => {
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
moderationQueues: {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: sinon.stub().callsFake(data => {
|
||||
expectAndFail(data).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
await waitForElement(() => getByTestID("moderate-container"));
|
||||
expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders reported queue with comments and load more", async () => {
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
moderationQueues: {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake(data => {
|
||||
expectAndFail(data).toEqual({ first: 5 });
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () =>
|
||||
pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<
|
||||
ModerationQueueToCommentsResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
@@ -133,34 +104,75 @@ describe("reported queue", () => {
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
};
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake(data => {
|
||||
expectAndFail(data).toEqual({
|
||||
first: 10,
|
||||
after: reportedComments[1].createdAt,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[2],
|
||||
cursor: reportedComments[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
})
|
||||
),
|
||||
},
|
||||
}) as any,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
await waitForElement(() => getByTestID("moderate-container"));
|
||||
expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders reported queue with comments and load more", async () => {
|
||||
const moderationQueuesStub = pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<ModerationQueueToCommentsResolver>(
|
||||
({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
};
|
||||
default:
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 10,
|
||||
after: reportedComments[1].createdAt,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[2],
|
||||
cursor: reportedComments[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
) as any,
|
||||
},
|
||||
});
|
||||
|
||||
const testRenderer = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () => moderationQueuesStub,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const moderateContainer = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("moderate-container")
|
||||
);
|
||||
@@ -194,57 +206,62 @@ describe("reported queue", () => {
|
||||
});
|
||||
|
||||
it("accepts comment in reported queue", async () => {
|
||||
const acceptCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: reportedComments[0].id,
|
||||
commentRevisionID: reportedComments[0].revision.id,
|
||||
},
|
||||
const acceptCommentStub = createMutationResolverStub<
|
||||
MutationToAcceptCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: reportedComments[0].id,
|
||||
commentRevisionID: reportedComments[0].revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: reportedComments[0].id,
|
||||
status: "ACCEPTED",
|
||||
status: GQLCOMMENT_STATUS.ACCEPTED,
|
||||
},
|
||||
moderationQueues: merge({}, emptyModerationQueues, {
|
||||
moderationQueues: pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 1,
|
||||
},
|
||||
}),
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
moderationQueues: {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: sinon.stub().callsFake(data => {
|
||||
expectAndFail(data).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
const moderationQueuesStub = pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<ModerationQueueToCommentsResolver>(
|
||||
({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
) as any,
|
||||
},
|
||||
});
|
||||
|
||||
const testRenderer = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () => moderationQueuesStub,
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
acceptComment: acceptCommentStub,
|
||||
},
|
||||
Mutation: {
|
||||
acceptComment: acceptCommentStub,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const testID = `moderate-comment-${reportedComments[0].id}`;
|
||||
@@ -271,57 +288,61 @@ describe("reported queue", () => {
|
||||
});
|
||||
|
||||
it("rejects comment in reported queue", async () => {
|
||||
const rejectCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: reportedComments[0].id,
|
||||
commentRevisionID: reportedComments[0].revision.id,
|
||||
},
|
||||
const rejectCommentStub = createMutationResolverStub<
|
||||
MutationToRejectCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: reportedComments[0].id,
|
||||
commentRevisionID: reportedComments[0].revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: reportedComments[0].id,
|
||||
status: "REJECTED",
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
},
|
||||
moderationQueues: merge({}, emptyModerationQueues, {
|
||||
moderationQueues: pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 1,
|
||||
},
|
||||
}),
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
moderationQueues: {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: sinon.stub().callsFake(data => {
|
||||
expectAndFail(data).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () =>
|
||||
pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: createQueryResolverStub<
|
||||
ModerationQueueToCommentsResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: reportedComments[1],
|
||||
cursor: reportedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}) as any,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
rejectComment: rejectCommentStub,
|
||||
},
|
||||
Mutation: {
|
||||
rejectComment: rejectCommentStub,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const testID = `moderate-comment-${reportedComments[0].id}`;
|
||||
@@ -355,30 +376,32 @@ describe("rejected queue", () => {
|
||||
|
||||
it("renders rejected queue with comments", async () => {
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
comments: sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
comments: ({ variables }) => {
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
await waitForElement(() => getByTestID("moderate-container"));
|
||||
@@ -387,53 +410,53 @@ describe("rejected queue", () => {
|
||||
|
||||
it("renders rejected queue with comments and load more", async () => {
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
comments: createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake((_, data) => {
|
||||
expectAndFail(data).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
comments: ({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 5,
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
};
|
||||
default:
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 10,
|
||||
after: rejectedComments[1].createdAt,
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[2],
|
||||
cursor: rejectedComments[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
};
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_, data) => {
|
||||
expectAndFail(data).toEqual({
|
||||
first: 10,
|
||||
after: rejectedComments[1].createdAt,
|
||||
status: "REJECTED",
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[2],
|
||||
cursor: rejectedComments[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
})
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const moderateContainer = await waitForElement(() =>
|
||||
@@ -469,55 +492,56 @@ describe("rejected queue", () => {
|
||||
});
|
||||
|
||||
it("accepts comment in rejected queue", async () => {
|
||||
const acceptCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: rejectedComments[0].id,
|
||||
commentRevisionID: rejectedComments[0].revision.id,
|
||||
},
|
||||
const acceptCommentStub = createMutationResolverStub<
|
||||
MutationToAcceptCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: rejectedComments[0].id,
|
||||
commentRevisionID: rejectedComments[0].revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: rejectedComments[0].id,
|
||||
status: "ACCEPTED",
|
||||
status: GQLCOMMENT_STATUS.ACCEPTED,
|
||||
},
|
||||
moderationQueues: merge({}, emptyModerationQueues, {
|
||||
moderationQueues: pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 1,
|
||||
},
|
||||
}),
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
comments: sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
comments: ({ variables }) => {
|
||||
expectAndFail(variables).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: rejectedComments[0],
|
||||
cursor: rejectedComments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
{
|
||||
node: rejectedComments[1],
|
||||
cursor: rejectedComments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: rejectedComments[1].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
Mutation: {
|
||||
acceptComment: acceptCommentStub,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
acceptComment: acceptCommentStub,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const testID = `moderate-comment-${rejectedComments[0].id}`;
|
||||
@@ -546,10 +570,12 @@ describe("rejected queue", () => {
|
||||
|
||||
describe("single comment view", () => {
|
||||
const comment = rejectedComments[0];
|
||||
const commentStub = sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toEqual({ id: comment.id });
|
||||
return reportedComments[0];
|
||||
});
|
||||
const commentStub = createQueryResolverStub<QueryToCommentResolver>(
|
||||
({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ id: comment.id });
|
||||
return reportedComments[0];
|
||||
}
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
replaceHistoryLocation(
|
||||
@@ -559,8 +585,10 @@ describe("single comment view", () => {
|
||||
|
||||
it("renders single comment view", async () => {
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
comment: commentStub,
|
||||
resolvers: {
|
||||
Query: {
|
||||
comment: commentStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
@@ -571,29 +599,30 @@ describe("single comment view", () => {
|
||||
});
|
||||
|
||||
it("accepts single comment", async () => {
|
||||
const acceptCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
},
|
||||
const acceptCommentStub = createMutationResolverStub<
|
||||
MutationToAcceptCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: comment.id,
|
||||
status: "ACCEPTED",
|
||||
status: GQLCOMMENT_STATUS.ACCEPTED,
|
||||
},
|
||||
moderationQueues: emptyModerationQueues,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
comment: commentStub,
|
||||
},
|
||||
Mutation: {
|
||||
acceptComment: acceptCommentStub,
|
||||
resolvers: {
|
||||
Query: {
|
||||
comment: commentStub,
|
||||
},
|
||||
Mutation: {
|
||||
acceptComment: acceptCommentStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -609,29 +638,30 @@ describe("single comment view", () => {
|
||||
});
|
||||
|
||||
it("rejects single comment", async () => {
|
||||
const rejectCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
},
|
||||
const rejectCommentStub = createMutationResolverStub<
|
||||
MutationToRejectCommentResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: comment.id,
|
||||
status: "REJECTED",
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
},
|
||||
moderationQueues: emptyModerationQueues,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
comment: commentStub,
|
||||
},
|
||||
Mutation: {
|
||||
rejectComment: rejectCommentStub,
|
||||
resolvers: {
|
||||
Query: {
|
||||
comment: commentStub,
|
||||
},
|
||||
Mutation: {
|
||||
rejectComment: rejectCommentStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { get, merge } from "lodash";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import {
|
||||
createSinonStub,
|
||||
createMutationResolverStub,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
findParentWithType,
|
||||
replaceHistoryLocation,
|
||||
waitForElement,
|
||||
@@ -11,7 +12,12 @@ import {
|
||||
within,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import { GQLSTORY_STATUS } from "talk-framework/schema";
|
||||
import {
|
||||
GQLResolver,
|
||||
GQLSTORY_STATUS,
|
||||
MutationToCloseStoryResolver,
|
||||
MutationToOpenStoryResolver,
|
||||
} from "talk-framework/schema";
|
||||
import create from "../create";
|
||||
import {
|
||||
emptyStories,
|
||||
@@ -21,38 +27,43 @@ import {
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
beforeEach(async () => {
|
||||
replaceHistoryLocation("http://localhost/admin/stories");
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
settings: sinon
|
||||
.stub()
|
||||
.returns(merge({}, settings, get(resolver, "Query.settings"))),
|
||||
stories: sinon.stub().callsFake((_, data) => {
|
||||
expectAndFail(data.status).toBeFalsy();
|
||||
return storyConnection;
|
||||
}),
|
||||
viewer: sinon.stub().returns(users.admins[0]),
|
||||
...resolver.Query,
|
||||
},
|
||||
};
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
stories: ({ variables }) => {
|
||||
expectAndFail(variables.status).toBeFalsy();
|
||||
return storyConnection;
|
||||
},
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const container = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("stories-container")
|
||||
);
|
||||
return { testRenderer, container };
|
||||
};
|
||||
}
|
||||
|
||||
it("renders stories", async () => {
|
||||
const { container } = await createTestRenderer();
|
||||
@@ -61,25 +72,30 @@ it("renders stories", async () => {
|
||||
|
||||
it("renders empty stories", async () => {
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
users: sinon.stub().returns(emptyStories),
|
||||
},
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
users: () => emptyStories,
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(within(container).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("filter by status", async () => {
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
stories: createSinonStub(
|
||||
s => s.onFirstCall().returns(storyConnection),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_, data) => {
|
||||
expectAndFail(data.status).toBe(GQLSTORY_STATUS.CLOSED);
|
||||
return emptyStories;
|
||||
})
|
||||
),
|
||||
},
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
stories: ({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
return storyConnection;
|
||||
default:
|
||||
expectAndFail(variables.status).toBe(GQLSTORY_STATUS.CLOSED);
|
||||
return emptyStories;
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const selectField = within(container).getByLabelText("Search by status");
|
||||
@@ -100,38 +116,42 @@ it("filter by status", async () => {
|
||||
|
||||
it("change story status", async () => {
|
||||
const story = stories[1];
|
||||
const openStory = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input).toMatchObject({
|
||||
id: story.id,
|
||||
});
|
||||
const storyRecord = merge({}, story, {
|
||||
status: GQLSTORY_STATUS.OPEN,
|
||||
createdAt: false,
|
||||
isClosed: false,
|
||||
});
|
||||
return {
|
||||
story: storyRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
const openStory = createMutationResolverStub<MutationToOpenStoryResolver>(
|
||||
({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
id: story.id,
|
||||
});
|
||||
const storyRecord = pureMerge(story, {
|
||||
status: GQLSTORY_STATUS.OPEN,
|
||||
createdAt: false,
|
||||
isClosed: false,
|
||||
});
|
||||
return {
|
||||
story: storyRecord,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const closeStory = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input).toMatchObject({
|
||||
id: story.id,
|
||||
});
|
||||
const storyRecord = merge({}, story, {
|
||||
status: GQLSTORY_STATUS.CLOSED,
|
||||
createdAt: "2018-11-29T16:01:51.897Z",
|
||||
isClosed: true,
|
||||
});
|
||||
return {
|
||||
story: storyRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
const closeStory = createMutationResolverStub<MutationToCloseStoryResolver>(
|
||||
({ variables }) => {
|
||||
expectAndFail(variables).toMatchObject({
|
||||
id: story.id,
|
||||
});
|
||||
const storyRecord = pureMerge(story, {
|
||||
status: GQLSTORY_STATUS.CLOSED,
|
||||
createdAt: "2018-11-29T16:01:51.897Z",
|
||||
isClosed: true,
|
||||
});
|
||||
return {
|
||||
story: storyRecord,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const { container } = await createTestRenderer({
|
||||
Mutation: { openStory, closeStory },
|
||||
resolvers: {
|
||||
Mutation: { openStory, closeStory },
|
||||
},
|
||||
});
|
||||
|
||||
const storyRow = within(container).getByText(story.metadata!.title!, {
|
||||
@@ -174,23 +194,33 @@ it("change story status", async () => {
|
||||
|
||||
it("load more", async () => {
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
stories: createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().returns({
|
||||
edges: [
|
||||
{ node: stories[0], cursor: stories[0].createdAt },
|
||||
{ node: stories[1], cursor: stories[1].createdAt },
|
||||
],
|
||||
pageInfo: { endCursor: stories[1].createdAt, hasNextPage: true },
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().returns({
|
||||
edges: [{ node: stories[2], cursor: stories[2].createdAt }],
|
||||
pageInfo: { endCursor: stories[2].createdAt, hasNextPage: false },
|
||||
})
|
||||
),
|
||||
},
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
stories: ({ callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
return {
|
||||
edges: [
|
||||
{ node: stories[0], cursor: stories[0].createdAt },
|
||||
{ node: stories[1], cursor: stories[1].createdAt },
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: stories[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
edges: [{ node: stories[2], cursor: stories[2].createdAt }],
|
||||
pageInfo: {
|
||||
endCursor: stories[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const loadMore = within(container).getByText("Load More");
|
||||
TestRenderer.act(() => {
|
||||
@@ -206,16 +236,19 @@ it("load more", async () => {
|
||||
|
||||
it("filter by search", async () => {
|
||||
const { container } = await createTestRenderer({
|
||||
Query: {
|
||||
stories: createSinonStub(
|
||||
s => s.onFirstCall().returns(storyConnection),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_, data) => {
|
||||
expectAndFail(data.query).toBe("search");
|
||||
return emptyStories;
|
||||
})
|
||||
),
|
||||
},
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
stories: ({ variables, callCount }) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
return storyConnection;
|
||||
default:
|
||||
expectAndFail(variables.query).toBe("search");
|
||||
return emptyStories;
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const searchField = within(container).getByLabelText(
|
||||
|
||||
@@ -47,12 +47,11 @@ const ConfirmEmailField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -49,12 +49,11 @@ const SetPasswordField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -44,12 +44,11 @@ const EmailField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -50,12 +50,11 @@ const SetPasswordField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -48,12 +48,11 @@ const CreateUsernameField: StatelessComponent<Props> = props => (
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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"))),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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<any, any>;
|
||||
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(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppQuery />
|
||||
</TalkContextProvider>
|
||||
);
|
||||
|
||||
return { context, testRenderer };
|
||||
export default function create(params: CreateTestRendererParams) {
|
||||
return createTestRenderer("auth", <AppQuery />, params);
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { IResolvers } from "graphql-tools";
|
||||
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
|
||||
import { createRelayEnvironment } from "talk-framework/testHelpers";
|
||||
|
||||
interface CreateEnvironmentParams {
|
||||
logNetwork?: boolean;
|
||||
muteNetworkErrors?: boolean;
|
||||
resolvers?: IResolvers<any, any>;
|
||||
initLocalState?: (
|
||||
local: RecordProxy,
|
||||
source: RecordSourceProxy,
|
||||
environment: Environment
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default function createEnvironment(params: CreateEnvironmentParams) {
|
||||
return createRelayEnvironment({
|
||||
network: {
|
||||
logNetwork: params.logNetwork,
|
||||
muteNetworkErrors: params.muteNetworkErrors,
|
||||
resolvers: params.resolvers || {},
|
||||
projectName: "tenant",
|
||||
},
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import path from "path";
|
||||
|
||||
import { createFluentBundle } from "talk-framework/testHelpers";
|
||||
|
||||
export default function create() {
|
||||
return createFluentBundle(
|
||||
"auth",
|
||||
path.resolve(__dirname, "../../../../locales/en-US")
|
||||
);
|
||||
}
|
||||
@@ -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"))),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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"))),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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"))),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -82,12 +82,11 @@ const ForgotPassword: StatelessComponent<ForgotPasswordForm> = props => {
|
||||
disabled={submitting}
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -67,12 +67,11 @@ const SignInWithEmail: StatelessComponent<SignInWithEmailForm> = props => {
|
||||
fullWidth
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
<Flex justifyContent="flex-end">
|
||||
<Localized id="signIn-forgotYourPassword">
|
||||
<Button
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createInMemoryStorage } from "talk-framework/lib/storage";
|
||||
import withPymStorage from "./withPymStorage";
|
||||
|
||||
class PymStub {
|
||||
public listeners: Record<string, ((msg: string) => void)> = {};
|
||||
public listeners: Record<string, (msg: string) => void> = {};
|
||||
public messages: Array<{ key: string; value: string }> = [];
|
||||
public type: string;
|
||||
|
||||
|
||||
@@ -114,8 +114,8 @@ function valueToState(
|
||||
// Start from the first unit,
|
||||
// keep first unit if value is set to 0,
|
||||
// otherwise use better matching unit if the value is fully dividable by the unit.
|
||||
unit = units.reduce(
|
||||
(x, cur) => (parsed % cur === 0 && parsed !== 0 ? cur : x)
|
||||
unit = units.reduce((x, cur) =>
|
||||
parsed % cur === 0 && parsed !== 0 ? cur : x
|
||||
);
|
||||
}
|
||||
// Compute new value relative to the selected unit.
|
||||
|
||||
@@ -6,9 +6,9 @@ import { PasswordField as PasswordFieldUI } from "talk-ui/components";
|
||||
|
||||
export interface Props
|
||||
extends Omit<
|
||||
PropTypesOf<typeof PasswordFieldUI>,
|
||||
"showPasswordTitle" | "hidePasswordTitle"
|
||||
> {}
|
||||
PropTypesOf<typeof PasswordFieldUI>,
|
||||
"showPasswordTitle" | "hidePasswordTitle"
|
||||
> {}
|
||||
|
||||
const PasswordField: StatelessComponent<Props> = props => (
|
||||
<Localized
|
||||
|
||||
@@ -26,10 +26,10 @@ import { PostMessageService } from "../postMessage";
|
||||
import SendPymReady from "./SendPymReady";
|
||||
import { TalkContext, TalkContextProvider } from "./TalkContext";
|
||||
|
||||
export type InitLocalState = ((
|
||||
export type InitLocalState = (
|
||||
environment: Environment,
|
||||
context: TalkContext
|
||||
) => void | Promise<void>);
|
||||
) => void | Promise<void>;
|
||||
|
||||
interface CreateContextArguments {
|
||||
/** Locales that the user accepts, usually `navigator.languages`. */
|
||||
@@ -104,7 +104,7 @@ function createRelayEnvironment() {
|
||||
return { environment, tokenGetter };
|
||||
}
|
||||
|
||||
function createRestAPI(tokenGetter: (() => string)) {
|
||||
function createRestAPI(tokenGetter: () => string) {
|
||||
return new RestClient("/api", tokenGetter);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ import React from "react";
|
||||
interface Props {
|
||||
form: FormApi;
|
||||
rootKey?: string;
|
||||
children: (
|
||||
params: {
|
||||
onInitValues: (data: any) => void;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
children: (params: { onInitValues: (data: any) => void }) => React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,11 +7,9 @@ import { AddSubmitHook, SubmitHook, SubmitHookContextProvider } from "./";
|
||||
|
||||
interface Props {
|
||||
onExecute: (data: any, form: FormApi) => Promise<void>;
|
||||
children: (
|
||||
params: {
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
children: (params: {
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
}) => React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ export interface BundledLocales {
|
||||
}
|
||||
|
||||
export interface LoadableLocales {
|
||||
[locale: string]: (() => Promise<string>);
|
||||
[locale: string]: () => Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,8 @@ import { TalkContext, withContext } from "../bootstrap";
|
||||
* and the signature (input: I) => Promise<R>. Calling
|
||||
* this will call the specified `commit` callback with
|
||||
* the Relay `environment` provided by the context.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
function createMutationContainer<T extends string, I, R>(
|
||||
propName: T,
|
||||
|
||||
@@ -9,8 +9,8 @@ type RecordSourceProxy<T> = T extends object
|
||||
readonly [P in keyof T]?: T[P] extends Array<infer U>
|
||||
? ReadonlyArray<RecordSourceProxy<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<RecordSourceProxy<V>>
|
||||
: RecordSourceProxy<T[P]>
|
||||
? ReadonlyArray<RecordSourceProxy<V>>
|
||||
: RecordSourceProxy<T[P]>
|
||||
}
|
||||
: T;
|
||||
|
||||
|
||||
@@ -35,14 +35,18 @@ export type MutationProp<
|
||||
> = T extends Mutation<any, infer I, infer R>
|
||||
? Parameters<T["commit"]>[1] extends undefined
|
||||
? () => R
|
||||
: keyof Parameters<T["commit"]>[1] extends never ? () => R : (input: I) => R
|
||||
: keyof Parameters<T["commit"]>[1] extends never
|
||||
? () => R
|
||||
: (input: I) => R
|
||||
: never;
|
||||
|
||||
type RemoveClientMutationID<T> = T extends Promise<infer U>
|
||||
? Promise<
|
||||
U extends { clientMutationId: any } ? Omit<U, "clientMutationId"> : U
|
||||
>
|
||||
: T extends { clientMutationId: any } ? Omit<T, "clientMutationId"> : T;
|
||||
: T extends { clientMutationId: any }
|
||||
? Omit<T, "clientMutationId">
|
||||
: T;
|
||||
|
||||
export function createMutation<N extends string, I, R>(
|
||||
name: N,
|
||||
|
||||
@@ -10,21 +10,18 @@ export default function useLoadMore(
|
||||
count: number
|
||||
): [() => void, boolean] {
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const loadMore = useCallback(
|
||||
() => {
|
||||
if (!relay.hasMore() || relay.isLoading()) {
|
||||
return;
|
||||
const loadMore = useCallback(() => {
|
||||
if (!relay.hasMore() || relay.isLoading()) {
|
||||
return;
|
||||
}
|
||||
setIsLoadingMore(true);
|
||||
relay.loadMore(count, error => {
|
||||
setIsLoadingMore(false);
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
setIsLoadingMore(true);
|
||||
relay.loadMore(count, error => {
|
||||
setIsLoadingMore(false);
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
[relay]
|
||||
);
|
||||
});
|
||||
}, [relay]);
|
||||
return [loadMore, isLoadingMore];
|
||||
}
|
||||
|
||||
@@ -15,34 +15,31 @@ export default function useRefetch<V = Variables>(
|
||||
): [() => void, boolean] {
|
||||
const [manualRefetchCount, setManualRefetchCount] = useState(0);
|
||||
const [refetching, setRefetching] = useState(false);
|
||||
useEffectAfterMount(
|
||||
() => {
|
||||
setRefetching(true);
|
||||
const disposable = relay.refetchConnection(
|
||||
10,
|
||||
error => {
|
||||
setRefetching(false);
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
variables
|
||||
);
|
||||
return () => {
|
||||
if (disposable) {
|
||||
disposable.dispose();
|
||||
useEffectAfterMount(() => {
|
||||
setRefetching(true);
|
||||
const disposable = relay.refetchConnection(
|
||||
10,
|
||||
error => {
|
||||
setRefetching(false);
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
},
|
||||
[
|
||||
relay,
|
||||
manualRefetchCount,
|
||||
...Object.keys(variables).reduce<any[]>((a, k) => {
|
||||
a.push((variables as any)[k]);
|
||||
return a;
|
||||
}, []),
|
||||
]
|
||||
);
|
||||
},
|
||||
variables
|
||||
);
|
||||
return () => {
|
||||
if (disposable) {
|
||||
disposable.dispose();
|
||||
}
|
||||
};
|
||||
}, [
|
||||
relay,
|
||||
manualRefetchCount,
|
||||
...Object.keys(variables).reduce<any[]>((a, k) => {
|
||||
a.push((variables as any)[k]);
|
||||
return a;
|
||||
}, []),
|
||||
]);
|
||||
return [() => setManualRefetchCount(manualRefetchCount + 1), refetching];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import createPymStorage from "./PymStorage";
|
||||
|
||||
class PymStub {
|
||||
public listeners: Record<string, ((msg: string) => void)> = {};
|
||||
public listeners: Record<string, (msg: string) => void> = {};
|
||||
public messages: Array<{ key: string; value: string }> = [];
|
||||
public type: string;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class PymStorage implements PromisifiedStorage {
|
||||
/** A Map of requestID => {resolve, reject} */
|
||||
private requests: Record<
|
||||
string,
|
||||
{ resolve: ((v: any) => void); reject: ((v: any) => void) }
|
||||
{ resolve: (v: any) => void; reject: (v: any) => void }
|
||||
> = {};
|
||||
|
||||
/** Requests method with parameters over pym. */
|
||||
|
||||
@@ -1,24 +1,46 @@
|
||||
import { merge } from "lodash";
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { DeepPartial } from "talk-framework/types";
|
||||
|
||||
/**
|
||||
* Fixture prepares schema type to be used in fixtures.
|
||||
* It adds an optional `__typename` to the schema type and
|
||||
* marks fields as optional.
|
||||
* Callbackify turns Fields e.g. `{a: string}`
|
||||
* to also allow a callback `{a: string | () => string}`
|
||||
*/
|
||||
export type Fixture<T> = T extends object
|
||||
export type Callbackify<T> = T extends object
|
||||
?
|
||||
| {
|
||||
[P in keyof T]: T[P] extends Array<infer U>
|
||||
? Array<Callbackify<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<Callbackify<V>>
|
||||
: Callbackify<T[P]>
|
||||
}
|
||||
| (() => {
|
||||
[P in keyof T]: T[P] extends Array<infer U>
|
||||
? Array<Callbackify<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<Callbackify<V>>
|
||||
: Callbackify<T[P]>
|
||||
})
|
||||
: T | (() => T);
|
||||
|
||||
/**
|
||||
* WithTypename adds `__typename` to allowed props deeply.
|
||||
*/
|
||||
export type WithTypename<T> = T extends object
|
||||
? {
|
||||
// (cvle): We don't use & { __typename?: string } because for some reason
|
||||
// typescript would allow field names that are not defined!
|
||||
[P in keyof T | "__typename"]?: P extends keyof T
|
||||
? T[P] extends Array<infer U>
|
||||
? Array<Fixture<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<Fixture<V>>
|
||||
: Fixture<T[P]>
|
||||
: string
|
||||
}
|
||||
[P in keyof T]: T[P] extends Array<infer U>
|
||||
? Array<WithTypename<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<WithTypename<V>>
|
||||
: WithTypename<T[P]>
|
||||
} & { __typename?: string }
|
||||
: T;
|
||||
|
||||
/**
|
||||
* Fixture adds typenames and is deeply partial.
|
||||
*/
|
||||
export type Fixture<T> = DeepPartial<WithTypename<T>>;
|
||||
|
||||
/**
|
||||
* createFixture lets you input the data of a schema object as deep partial
|
||||
* including it's `__typename`, merged it with `base` and return it as the
|
||||
@@ -26,9 +48,12 @@ export type Fixture<T> = T extends object
|
||||
* to only include fields that exists in `data` and `base` though to
|
||||
* type this it seems we need partial generic inferation support.
|
||||
*/
|
||||
export default function createFixture<T>(data: Fixture<T>, base?: T): T {
|
||||
export default function createFixture<T>(
|
||||
data: Fixture<T>,
|
||||
base?: T
|
||||
): WithTypename<T> {
|
||||
if (base) {
|
||||
return merge({}, base, data);
|
||||
return pureMerge(base, data) as any;
|
||||
}
|
||||
return data as T;
|
||||
return data as any;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import createFixture, { Fixture } from "./createFixture";
|
||||
import createFixture, { Fixture, WithTypename } from "./createFixture";
|
||||
|
||||
/**
|
||||
* createFixtures lets you input an array of data of a schema object as deep partial
|
||||
@@ -10,6 +10,6 @@ import createFixture, { Fixture } from "./createFixture";
|
||||
export default function createFixtures<T>(
|
||||
data: Array<Fixture<T>>,
|
||||
base?: T
|
||||
): T[] {
|
||||
return data.map(d => createFixture(d, base)) as T[];
|
||||
): Array<WithTypename<T>> {
|
||||
return data.map(d => createFixture(d, base)) as any;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
import { identity, omit } from "lodash";
|
||||
import sinon from "sinon";
|
||||
import { Mutation } from "talk-framework/lib/relay/mutation";
|
||||
import { Omit } from "talk-framework/types";
|
||||
|
||||
import { Fixture } from "./createFixture";
|
||||
import { Resolver } from "./createTestRenderer";
|
||||
|
||||
export type NoClientMutationID<T> = T extends { clientMutationId: any }
|
||||
? Omit<T, "clientMutationId">
|
||||
: T;
|
||||
|
||||
export type MutationResult<T> = NoClientMutationID<Fixture<T>>;
|
||||
export type MutationResultVariations<T> = T extends Resolver<any, infer R>
|
||||
? MutationResult<R>
|
||||
: never;
|
||||
|
||||
export type MutationResolverCallback<T extends Resolver<any, any>> = (data: {
|
||||
variables: T extends Resolver<infer V, any>
|
||||
? V extends { input: infer W }
|
||||
? NoClientMutationID<W>
|
||||
: never
|
||||
: never;
|
||||
callCount: number;
|
||||
typecheck: (data: MutationResultVariations<T>) => MutationResultVariations<T>;
|
||||
}) => MutationResultVariations<T>;
|
||||
|
||||
/**
|
||||
* createMutationResolverStub makes it easier to write a SinonStub.
|
||||
* Given a `ResolverType` from the Schema it'll provide types as well!.
|
||||
*/
|
||||
export default function createMutationResolverStub<
|
||||
T extends Mutation<any, any, any>
|
||||
>(
|
||||
callback: (
|
||||
variables: T extends Mutation<any, infer I, any> ? I : never,
|
||||
callCount: number
|
||||
) => T extends Mutation<any, any, infer R>
|
||||
? R extends Promise<infer U> ? U | R : R | Promise<R>
|
||||
: never
|
||||
) {
|
||||
T extends Resolver<any, any>
|
||||
>(callback: MutationResolverCallback<T>) {
|
||||
let callCount = 0;
|
||||
const lastClientMutationIds: any[] = [];
|
||||
const resolver = async (_: any, data: any) => {
|
||||
@@ -18,7 +38,11 @@ export default function createMutationResolverStub<
|
||||
expectAndFail(clientMutationId).toBeTruthy();
|
||||
expectAndFail(lastClientMutationIds).not.toContain(clientMutationId);
|
||||
lastClientMutationIds.push(clientMutationId);
|
||||
const result = await callback(data.input, callCount++);
|
||||
const result = await callback({
|
||||
variables: omit(data.input, "clientMutationId"),
|
||||
callCount: callCount++,
|
||||
typecheck: identity,
|
||||
});
|
||||
expectAndFail(result.clientMutationId).toBeUndefined();
|
||||
result.clientMutationId = clientMutationId;
|
||||
return result;
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
import { identity } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
type Resolver<V, R> = (parent: any, args: V, context: any, info: any) => R;
|
||||
import { Fixture } from "./createFixture";
|
||||
import { Resolver } from "./createTestRenderer";
|
||||
|
||||
export type QueryResult<T> = Fixture<T>;
|
||||
export type QueryResultVariations<
|
||||
T extends Resolver<any, any>
|
||||
> = T extends Resolver<any, infer R> ? QueryResult<R> : never;
|
||||
|
||||
export type QueryResolverCallback<T extends Resolver<any, any>> = (data: {
|
||||
variables: T extends Resolver<infer V, any> ? V : never;
|
||||
callCount: number;
|
||||
typecheck: (data: QueryResultVariations<T>) => QueryResultVariations<T>;
|
||||
}) => QueryResultVariations<T>;
|
||||
|
||||
/**
|
||||
* createQueryResolverStub makes it easier to write a SinonStub.
|
||||
* Given a `ResolverType` from the Schema it'll provide types as well!.
|
||||
*/
|
||||
export default function createQueryResolverStub<T extends Resolver<any, any>>(
|
||||
callback: (
|
||||
variables: T extends Resolver<infer V, any> ? V : never,
|
||||
callCount: number
|
||||
) => T extends Resolver<any, infer R>
|
||||
? R extends Promise<infer U> ? U | R : R | Promise<R>
|
||||
: never
|
||||
callback: QueryResolverCallback<T>
|
||||
) {
|
||||
let callCount = 0;
|
||||
return sinon
|
||||
.stub()
|
||||
.callsFake((_: any, data: any) => callback(data, callCount++));
|
||||
return sinon.stub().callsFake((fallback: any, variables: any) => {
|
||||
return callback({
|
||||
variables: variables || fallback,
|
||||
callCount: callCount++,
|
||||
typecheck: identity,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface CreateRelayEnvironmentNetworkParams {
|
||||
/** project name of graphql-config */
|
||||
projectName: string;
|
||||
/** graphql resolvers */
|
||||
resolvers: IResolvers<any, any>;
|
||||
resolvers?: IResolvers<any, any>;
|
||||
/** If enabled, graphql responses will be logged to the console */
|
||||
logNetwork?: boolean;
|
||||
/** If enabled, graphql errors will be muted */
|
||||
@@ -99,7 +99,7 @@ export default function createRelayEnvironment(
|
||||
if (params.network) {
|
||||
const schema = loadSchema(
|
||||
params.network.projectName,
|
||||
params.network.resolvers,
|
||||
params.network.resolvers || {},
|
||||
{ requireResolversForResolveType: false }
|
||||
);
|
||||
network = Network.create(
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { mapValues } from "lodash";
|
||||
import { SinonStub } from "sinon";
|
||||
|
||||
import createMutationResolverStub, {
|
||||
MutationResolverCallback,
|
||||
} from "./createMutationResolverStub";
|
||||
import createQueryResolverStub, {
|
||||
QueryResolverCallback,
|
||||
} from "./createQueryResolverStub";
|
||||
import { Resolvers } from "./createTestRenderer";
|
||||
|
||||
export interface ResolversTemplate<T extends Resolvers = any> {
|
||||
Query?: {
|
||||
[P in keyof Required<T>["Query"]]: QueryResolverCallback<
|
||||
Required<T>["Query"][P]
|
||||
>
|
||||
};
|
||||
Mutation?: {
|
||||
[P in keyof Required<T>["Mutation"]]: MutationResolverCallback<
|
||||
Required<T>["Mutation"][P]
|
||||
>
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResolversStub<T extends Resolvers = any> {
|
||||
Query?: { [P in keyof Required<T>["Query"]]: SinonStub };
|
||||
Mutation?: { [P in keyof Required<T>["Mutation"]]: SinonStub };
|
||||
}
|
||||
|
||||
function isSinonStub(v: any) {
|
||||
return v.called !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* createResolversStub is a helper for creating resolvers.
|
||||
*
|
||||
* Instead of writing:
|
||||
* ```ts
|
||||
* {
|
||||
* Query: {
|
||||
* settings: createQueryResolverStub<QueryToSettingsResolver>(({ variables }) => settings)
|
||||
* users: createQueryResolverStub<QueryToUsersResolver>(() => users)
|
||||
* viewer: createQueryResolverStub<QueryToSettingsResolver>(() => viewer)
|
||||
* },
|
||||
* Mutation: {
|
||||
* // Same goes for Mutations
|
||||
* }
|
||||
* },
|
||||
* ```
|
||||
*
|
||||
* You can do
|
||||
* ```ts
|
||||
* createResolversStub<GQLResolver>({
|
||||
* Query: {
|
||||
* settings: ({ variables }) => settings
|
||||
* users: () => users
|
||||
* viewer: () => viewer
|
||||
* },
|
||||
* Mutation: {
|
||||
* // Same goes for Mutations
|
||||
* }
|
||||
* }),
|
||||
* ```
|
||||
*/
|
||||
export default function createResolversStub<T extends Resolvers = any>(
|
||||
resolvers: ResolversTemplate<T>
|
||||
): ResolversStub<T> {
|
||||
const result: any = {};
|
||||
if (resolvers.Query) {
|
||||
result.Query = mapValues(resolvers.Query, v =>
|
||||
typeof v === "function" && !isSinonStub(v)
|
||||
? createQueryResolverStub(v)
|
||||
: v
|
||||
);
|
||||
}
|
||||
if (resolvers.Mutation) {
|
||||
result.Mutation = mapValues(resolvers.Mutation, v =>
|
||||
typeof v === "function" && !isSinonStub(v)
|
||||
? createMutationResolverStub(v)
|
||||
: v
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { EventEmitter2 } from "eventemitter2";
|
||||
import { IResolvers } from "graphql-tools";
|
||||
import { noop } from "lodash";
|
||||
import path from "path";
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
|
||||
|
||||
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 createFluentBundle from "./createFluentBundle";
|
||||
import createRelayEnvironment from "./createRelayEnvironment";
|
||||
|
||||
export type Resolver<V, R> = (
|
||||
parent: any,
|
||||
args: V,
|
||||
context: any,
|
||||
info: any
|
||||
) => R;
|
||||
|
||||
export interface Resolvers<Q extends Resolver<any, any> = any, M = any> {
|
||||
Query?: Q;
|
||||
Mutation?: M;
|
||||
}
|
||||
|
||||
export interface TestResolvers<T extends Resolvers = any> {
|
||||
Query?: { [P in keyof Required<T>["Query"]]: (() => any) };
|
||||
Mutation?: { [P in keyof Required<T>["Mutation"]]: (() => any) };
|
||||
}
|
||||
|
||||
function createNodeMock(element: React.ReactElement<any>) {
|
||||
if (element.type === "div") {
|
||||
return {
|
||||
innerHtml: "",
|
||||
className: "",
|
||||
focus: noop,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface CreateTestRendererParams<T extends Resolvers = any> {
|
||||
logNetwork?: boolean;
|
||||
muteNetworkErrors?: boolean;
|
||||
resolvers?: TestResolvers<T>;
|
||||
browserInfo?: TalkContext["browserInfo"];
|
||||
initLocalState?: (
|
||||
local: RecordProxy,
|
||||
source: RecordSourceProxy,
|
||||
environment: Environment
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default function createTestRenderer<
|
||||
T extends { Query?: any; Mutation?: any } = any
|
||||
>(
|
||||
target: string,
|
||||
element: React.ReactNode,
|
||||
params: CreateTestRendererParams<T>
|
||||
) {
|
||||
const environment = createRelayEnvironment({
|
||||
network: {
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: params.logNetwork,
|
||||
resolvers: params.resolvers as IResolvers<any, any>,
|
||||
muteNetworkErrors: params.muteNetworkErrors,
|
||||
projectName: "tenant",
|
||||
},
|
||||
initLocalState: (localRecord, source, env) => {
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, env);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
locales: ["en-US"],
|
||||
localeBundles: [
|
||||
createFluentBundle(
|
||||
target,
|
||||
path.resolve(__dirname, "../../../../locales/en-US")
|
||||
),
|
||||
],
|
||||
localStorage: createPromisifiedStorage(),
|
||||
sessionStorage: createPromisifiedStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
browserInfo: params.browserInfo || { ios: false },
|
||||
uuidGenerator: createUUIDGenerator(),
|
||||
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
|
||||
clearSession: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
TestRenderer.act(() => {
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>{element}</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
});
|
||||
return { context, testRenderer: testRenderer! };
|
||||
}
|
||||
@@ -11,7 +11,6 @@ export {
|
||||
export { default as createUUIDGenerator } from "./createUUIDGenerator";
|
||||
export * from "./denormalize";
|
||||
export { default as limitSnapshotTo } from "./limitSnapshotTo";
|
||||
export { default as inputPredicate } from "./inputPredicate";
|
||||
export { default as within } from "./within";
|
||||
export { default as wait } from "./wait";
|
||||
export { default as waitForElement } from "./waitForElement";
|
||||
@@ -22,9 +21,18 @@ export { default as replaceHistoryLocation } from "./replaceHistoryLocation";
|
||||
export { default as createAccessToken } from "./createAccessToken";
|
||||
export { default as findParentsWithType } from "./findParentsWithType";
|
||||
export { default as findParentWithType } from "./findParentWithType";
|
||||
export { default as createFixture } from "./createFixture";
|
||||
export {
|
||||
default as createFixture,
|
||||
Fixture,
|
||||
WithTypename,
|
||||
} from "./createFixture";
|
||||
export { default as createFixtures } from "./createFixtures";
|
||||
export {
|
||||
default as createMutationResolverStub,
|
||||
} from "./createMutationResolverStub";
|
||||
export { default as createQueryResolverStub } from "./createQueryResolverStub";
|
||||
export {
|
||||
default as createTestRenderer,
|
||||
CreateTestRendererParams,
|
||||
} from "./createTestRenderer";
|
||||
export { default as createResolversStub } from "./createResolversStub";
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { ReactTestInstance } from "react-test-renderer";
|
||||
|
||||
const inputPredicate = (nameOrID: string) => (n: ReactTestInstance) => {
|
||||
return (
|
||||
[n.props.name, n.props.id].indexOf(nameOrID) > -1 &&
|
||||
["input", "button"].indexOf(n.type as string) > -1
|
||||
);
|
||||
};
|
||||
|
||||
export default inputPredicate;
|
||||
@@ -12,8 +12,8 @@ export type NoFragmentRefs<T> = T extends object
|
||||
? T extends ((...args: any[]) => any)
|
||||
? T
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<NoFragmentRefs2<U>> // TODO: (cvle) this should normally reference itself but it complains about a circular reference.
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
? ReadonlyArray<NoFragmentRefs2<U>> // TODO: (cvle) this should normally reference itself but it complains about a circular reference.
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
: T;
|
||||
|
||||
// TODO: (cvle) these NoFragmentRefX are a workaround for above issue
|
||||
@@ -21,16 +21,16 @@ export type NoFragmentRefs2<T> = T extends object
|
||||
? T extends ((...args: any[]) => any)
|
||||
? T
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<NoFragmentRefs3<U>>
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
? ReadonlyArray<NoFragmentRefs3<U>>
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
: T;
|
||||
|
||||
export type NoFragmentRefs3<T> = T extends object
|
||||
? T extends ((...args: any[]) => any)
|
||||
? T
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<NoFragmentRefs4<U>>
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
? ReadonlyArray<NoFragmentRefs4<U>>
|
||||
: { [P in keyof OmitFragments<T>]: NoFragmentRefs<T[P]> }
|
||||
: T;
|
||||
|
||||
export type NoFragmentRefs4<T> = T extends object
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user