mirror of
https://github.com/wassname/talk.git
synced 2026-07-09 20:30:45 +08:00
[CORL-159, CORL-160] Stream Config Tab (#2219)
* feat: Implement stream configuration tab * feat: split profile & configure into separate bundles * chore: better role logic * fix+chore: add test cases, implement expectAndFail, refactor tests * chore: add some comments * chore: Update src/core/client/framework/lib/form/helpers.tsx Co-Authored-By: cvle <vinh@wikiwi.io> * feat: support new graphql mutations/schema * fix: ci fixes * fix: improvement to revision loading * fix: updated some tests * fix: adapt client to changes * fix: remove obsolote isClosed in UpdateStory * ci: increase no_output_timeout for build
This commit is contained in:
+12
-6
@@ -107,6 +107,7 @@ jobs:
|
||||
- run:
|
||||
name: Build
|
||||
command: npm run build
|
||||
no_output_timeout: 20m
|
||||
- run:
|
||||
name: Verify Bundle Size
|
||||
command: npx bundlesize
|
||||
@@ -120,14 +121,18 @@ jobs:
|
||||
|
||||
# docker_tests will test that the docker build process completes.
|
||||
docker_tests:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
- image: node:10-alpine
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Build
|
||||
command: docker build -t coralproject/talk:next --build-arg REVISION_HASH=${CIRCLE_SHA1} .
|
||||
no_output_timeout: 20m
|
||||
name: Update Image Dependancies
|
||||
command: apk --no-cache add git python
|
||||
- run:
|
||||
name: Install NodeJS Dependancies
|
||||
command: npm install
|
||||
|
||||
# release_docker will build and push the Docker image.
|
||||
release_docker:
|
||||
@@ -140,7 +145,8 @@ jobs:
|
||||
command: docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
- run:
|
||||
name: Build
|
||||
command: docker build -t coralproject/talk:next --build-arg REVISION_HASH=${CIRCLE_SHA1} .
|
||||
command: docker build -t coralproject/talk:next --build-arg REVISION_HASH=$CIRCLE_SHA1 .
|
||||
no_output_timeout: 30m
|
||||
- run:
|
||||
name: Push
|
||||
command: docker push coralproject/talk:next
|
||||
|
||||
+5
-14
@@ -13,30 +13,21 @@ ENV NODE_ENV production
|
||||
# Bundle application source.
|
||||
COPY . /usr/src/app
|
||||
|
||||
# Store the current git revision.
|
||||
ARG REVISION_HASH
|
||||
RUN mkdir -p dist/core/common/__generated__ && \
|
||||
echo "{\"revision\": \"${REVISION_HASH}\"}" > dist/core/common/__generated__/revision.json
|
||||
|
||||
# Install build static assets and clear caches.
|
||||
RUN NODE_ENV=development npm install && \
|
||||
npm run generate && \
|
||||
npm run build && \
|
||||
npm prune --production
|
||||
|
||||
FROM node:10-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy the compiled source into the new stage.
|
||||
COPY --from=0 /usr/src/app .
|
||||
|
||||
# Setup the environment
|
||||
ENV PATH /usr/src/app/bin:$PATH
|
||||
ENV PORT 5000
|
||||
EXPOSE 5000
|
||||
ENV NODE_ENV production
|
||||
|
||||
# Store the current git revision.
|
||||
ARG REVISION_HASH
|
||||
RUN mkdir dist/core/common/__generated__ && \
|
||||
echo "{\"revision\": \"${REVISION_HASH}\"}" > dist/core/common/__generated__/revision.json
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
|
||||
Generated
+101
-49
@@ -2050,7 +2050,7 @@
|
||||
},
|
||||
"@types/accepts": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
||||
"resolved": "http://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
||||
"integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
@@ -2565,9 +2565,9 @@
|
||||
"integrity": "sha512-HtKGu+qG1NPvYe1z7ezLsyIaXYyi8SoAVqWDZgDQ8dLrsZvSzUNCwZyfX33uhWxL/SU0ZDQZ3nwZ0nimt507Kw=="
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "16.7.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.7.20.tgz",
|
||||
"integrity": "sha512-Qd5RWkwl6SL7R2XzLk/cicjVQm1Mhc6HqXY5Ei4pWd1Vi8Fkbd5O0sA398x8fRSTPAuHdDYD9nrWmJMYTJI0vQ==",
|
||||
"version": "16.8.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.7.tgz",
|
||||
"integrity": "sha512-0xbkIyrDNKUn4IJVf8JaCn+ucao/cq6ZB8O6kSzhrJub1cVSqgTArtG0qCfdERWKMEIvUbrwLXeQMqWEsyr9dA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -11493,21 +11493,21 @@
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"found": {
|
||||
"version": "0.3.14",
|
||||
"resolved": "https://registry.npmjs.org/found/-/found-0.3.14.tgz",
|
||||
"integrity": "sha512-Z4Ag0L8whIJwVf0MyNTkx9q3ekmbqK5RkCvlcVU7Dn3ruKfKSxbPReUiQi7mWGMTAut2JnkwiobSD/FQRhk9Vw==",
|
||||
"version": "0.3.21",
|
||||
"resolved": "https://registry.npmjs.org/found/-/found-0.3.21.tgz",
|
||||
"integrity": "sha512-iaeGVTBirrTAravFPcIfWPuJHIaFNqnJ87AMvnpB4Ig8RTB97C4PipVopmSxK+lLeMdEybUwaW+BhNDhLPEUSw==",
|
||||
"requires": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"farce": "^0.2.6",
|
||||
"is-promise": "^2.1.0",
|
||||
"lodash": "^4.17.5",
|
||||
"lodash": "^4.17.11",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"prop-types-extra": "^1.0.1",
|
||||
"react-redux": "^5.0.7",
|
||||
"prop-types": "^15.6.2",
|
||||
"prop-types-extra": "^1.1.0",
|
||||
"react-redux": "^5.1.1",
|
||||
"react-static-container": "^1.0.2",
|
||||
"redux": "^4.0.0",
|
||||
"warning": "^3.0.0"
|
||||
"redux": "^4.0.1",
|
||||
"warning": "^4.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
@@ -11515,6 +11515,19 @@
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"requires": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
|
||||
@@ -11523,10 +11536,19 @@
|
||||
"isarray": "0.0.1"
|
||||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz",
|
||||
"integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"symbol-observable": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
|
||||
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
@@ -13288,7 +13310,8 @@
|
||||
"hoist-non-react-statics": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
|
||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
|
||||
"dev": true
|
||||
},
|
||||
"home-or-tmp": {
|
||||
"version": "2.0.0",
|
||||
@@ -16852,7 +16875,7 @@
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -17119,7 +17142,8 @@
|
||||
"lodash-es": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz",
|
||||
"integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q=="
|
||||
"integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._reinterpolate": {
|
||||
"version": "3.0.0",
|
||||
@@ -19442,7 +19466,7 @@
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"dev": true
|
||||
}
|
||||
@@ -22919,14 +22943,14 @@
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.8.1.tgz",
|
||||
"integrity": "sha512-wLw5CFGPdo7p/AgteFz7GblI2JPOos0+biSoxf1FPsGxWQZdN/pj6oToJs1crn61DL3Ln7mN86uZ4j74p31ELQ==",
|
||||
"version": "16.8.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.8.4.tgz",
|
||||
"integrity": "sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.1"
|
||||
"scheduler": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"react-adopt": {
|
||||
@@ -23231,14 +23255,14 @@
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "16.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.1.tgz",
|
||||
"integrity": "sha512-N74IZUrPt6UiDjXaO7UbDDFXeUXnVhZzeRLy/6iqqN1ipfjrhR60Bp5NuBK+rv3GMdqdIuwIl22u1SYwf330bg==",
|
||||
"version": "16.8.4",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.4.tgz",
|
||||
"integrity": "sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.1"
|
||||
"scheduler": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"react-emotion": {
|
||||
@@ -23285,8 +23309,7 @@
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-lightweight-tooltip": {
|
||||
"version": "1.0.0",
|
||||
@@ -23356,16 +23379,45 @@
|
||||
}
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "http://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz",
|
||||
"integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==",
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.1.tgz",
|
||||
"integrity": "sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==",
|
||||
"requires": {
|
||||
"hoist-non-react-statics": "^2.5.0",
|
||||
"invariant": "^2.0.0",
|
||||
"lodash": "^4.17.5",
|
||||
"lodash-es": "^4.17.5",
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"invariant": "^2.2.4",
|
||||
"loose-envify": "^1.1.0",
|
||||
"prop-types": "^15.6.0"
|
||||
"prop-types": "^15.6.1",
|
||||
"react-is": "^16.6.0",
|
||||
"react-lifecycles-compat": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.4.tgz",
|
||||
"integrity": "sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.12.0"
|
||||
}
|
||||
},
|
||||
"hoist-non-react-statics": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
|
||||
"integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
|
||||
"requires": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.8.3",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz",
|
||||
"integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA=="
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
|
||||
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-relay": {
|
||||
@@ -23457,21 +23509,21 @@
|
||||
"integrity": "sha512-rxlZtZk5t6Y3gqqpaZ1lxY3RqlQcBU5uGsSoZj/hbF3ZweDqPbFHDkczT4emAxeaw37OD96RAAoayFGFQZCdWg=="
|
||||
},
|
||||
"react-test-renderer": {
|
||||
"version": "16.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.1.tgz",
|
||||
"integrity": "sha512-Bd21TN3+YVl6GZwav6O0T6m5UwGfOj+2+xZH5VH93ToD6M5uclN/c+R1DGX49ueG413KZPUx7Kw3sOYz2aJgfg==",
|
||||
"version": "16.8.4",
|
||||
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.4.tgz",
|
||||
"integrity": "sha512-jQ9Tf/ilIGSr55Cz23AZ/7H3ABEdo9oy2zF9nDHZyhLHDSLKuoILxw2ifpBfuuwQvj4LCoqdru9iZf7gwFH28A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.8.1",
|
||||
"scheduler": "^0.13.1"
|
||||
"react-is": "^16.8.4",
|
||||
"scheduler": "^0.13.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-is": {
|
||||
"version": "16.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.1.tgz",
|
||||
"integrity": "sha512-ioMCzVDWvCvKD8eeT+iukyWrBGrA3DiFYkXfBsVYIRdaREZuBjENG+KjrikavCLasozqRWTwFUagU/O4vPpRMA==",
|
||||
"version": "16.8.4",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.4.tgz",
|
||||
"integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -24689,9 +24741,9 @@
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.1.tgz",
|
||||
"integrity": "sha512-VJKOkiKIN2/6NOoexuypwSrybx13MY7NSy9RNt8wPvZDMRT1CW6qlpF5jXRToXNHz3uWzbm2elNpZfXfGPqP9A==",
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.4.tgz",
|
||||
"integrity": "sha512-cvSOlRPxOHs5dAhP9yiS/6IDmVAVxmk33f0CtTJRkmUWcb1Us+t7b1wqdzoC0REw2muC9V5f1L/w5R5uKGaepA==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
|
||||
+5
-5
@@ -67,7 +67,7 @@
|
||||
"express-static-gzip": "^0.3.2",
|
||||
"farce": "^0.2.6",
|
||||
"fluent": "^0.10.0",
|
||||
"found": "^0.3.14",
|
||||
"found": "^0.3.21",
|
||||
"found-relay": "^0.3.1",
|
||||
"fs-extra": "^6.0.1",
|
||||
"graphql": "^0.13.2",
|
||||
@@ -170,7 +170,7 @@
|
||||
"@types/passport-oauth2": "^1.4.5",
|
||||
"@types/passport-strategy": "^0.2.33",
|
||||
"@types/prop-types": "^15.5.8",
|
||||
"@types/react": "^16.7.20",
|
||||
"@types/react": "^16.8.7",
|
||||
"@types/react-copy-to-clipboard": "^4.2.5",
|
||||
"@types/react-dom": "^16.0.11",
|
||||
"@types/react-relay": "^1.3.9",
|
||||
@@ -263,15 +263,15 @@
|
||||
"pstree.remy": "^1.1.0",
|
||||
"pym.js": "^1.3.2",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.8.0",
|
||||
"react": "^16.8.4",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-dev-utils": "6.0.0-next.3e165448",
|
||||
"react-dom": "^16.8.0",
|
||||
"react-dom": "^16.8.4",
|
||||
"react-final-form": "^3.6.4",
|
||||
"react-popper": "^1.3.2",
|
||||
"react-relay": "^1.7.0-rc.1",
|
||||
"react-responsive": "^5.0.0",
|
||||
"react-test-renderer": "^16.8.0",
|
||||
"react-test-renderer": "^16.8.4",
|
||||
"react-timeago": "^4.1.9",
|
||||
"react-transition-group": "^2.5.0",
|
||||
"react-with-state-props": "^2.0.4",
|
||||
|
||||
@@ -8,8 +8,9 @@ import Configure from "./Configure";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof Configure> = {
|
||||
onSave: noop,
|
||||
onSubmit: noop,
|
||||
onChange: noop,
|
||||
children: <span />,
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<Configure {...props} />);
|
||||
|
||||
@@ -12,17 +12,18 @@ import { Link, Navigation } from "./Navigation";
|
||||
import SideBar from "./SideBar";
|
||||
|
||||
interface Props {
|
||||
onSave: (settings: any, form: FormApi) => void;
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
onChange: (formState: FormState) => void;
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
const Configure: StatelessComponent<Props> = ({
|
||||
onSave,
|
||||
onSubmit,
|
||||
onChange,
|
||||
children,
|
||||
}) => (
|
||||
<MainLayout data-testid="configure-container">
|
||||
<Form onSubmit={onSave}>
|
||||
<Form onSubmit={onSubmit}>
|
||||
{({ handleSubmit, submitting, pristine, form, submitError }) => (
|
||||
<form autoComplete="off" onSubmit={handleSubmit} id="configure-form">
|
||||
<FormSpy onChange={onChange} />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { FormApi, FormState } from "final-form";
|
||||
import { Router } from "found";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
@@ -7,117 +6,54 @@ import {
|
||||
UpdateSettingsMutation,
|
||||
withUpdateSettingsMutation,
|
||||
} from "talk-admin/mutations";
|
||||
import { TalkContext, withContext } from "talk-framework/lib/bootstrap";
|
||||
import { InvalidRequestError } from "talk-framework/lib/errors";
|
||||
import { getMessage } from "talk-framework/lib/i18n";
|
||||
import { SubmitHookHandler } from "talk-framework/lib/form";
|
||||
|
||||
import Configure from "../components/Configure";
|
||||
import {
|
||||
AddSubmitHook,
|
||||
SubmitHook,
|
||||
SubmitHookContextProvider,
|
||||
} from "../submitHook";
|
||||
import NavigationWarningContainer from "./NavigationWarningContainer";
|
||||
|
||||
interface Props {
|
||||
localeBundles: TalkContext["localeBundles"];
|
||||
router: Router;
|
||||
updateSettings: UpdateSettingsMutation;
|
||||
children: React.ReactNode;
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
class ConfigureContainer extends React.Component<Props> {
|
||||
private dirty = false;
|
||||
private removeTransitionHook: () => void;
|
||||
private submitHooks: SubmitHook[] = [];
|
||||
interface State {
|
||||
dirty: boolean;
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
class ConfigureContainer extends React.Component<Props, State> {
|
||||
public state: State = {
|
||||
dirty: false,
|
||||
};
|
||||
|
||||
this.dirty = false;
|
||||
|
||||
const warningMessage = getMessage(
|
||||
props.localeBundles,
|
||||
"configure-unsavedInputWarning",
|
||||
"You have unsaved input. Are you sure you want to leave this page?"
|
||||
);
|
||||
|
||||
this.removeTransitionHook = props.router.addTransitionHook(
|
||||
() => (this.dirty ? warningMessage : true)
|
||||
);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.removeTransitionHook();
|
||||
}
|
||||
|
||||
private handleSave = async (
|
||||
private handleExecute = async (
|
||||
data: UpdateSettingsInput["settings"],
|
||||
form: FormApi
|
||||
) => {
|
||||
let cancelled = false;
|
||||
let formErrors: Record<string, React.ReactNode> = {};
|
||||
const executeCallbacks: Array<() => Promise<any>> = [];
|
||||
const cancel = (errors: Record<string, React.ReactNode>) => {
|
||||
cancelled = true;
|
||||
formErrors = { ...errors, ...formErrors };
|
||||
};
|
||||
const onExecute = (cb: () => Promise<any>) => {
|
||||
executeCallbacks.push(cb);
|
||||
};
|
||||
try {
|
||||
// Call submit hooks, that can manipulate what
|
||||
// we send as the mutation.
|
||||
let nextData = data;
|
||||
for (const hook of this.submitHooks) {
|
||||
const result = await hook(nextData, { cancel, onExecute });
|
||||
if (result) {
|
||||
nextData = result;
|
||||
}
|
||||
}
|
||||
if (cancelled) {
|
||||
return formErrors;
|
||||
}
|
||||
|
||||
executeCallbacks.push(() =>
|
||||
this.props.updateSettings({ settings: nextData })
|
||||
);
|
||||
for (const c of executeCallbacks.map(cb => cb())) {
|
||||
await c;
|
||||
}
|
||||
form.initialize(data);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidRequestError) {
|
||||
return error.invalidArgs;
|
||||
}
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
return undefined;
|
||||
await this.props.updateSettings({ settings: data });
|
||||
form.initialize(data);
|
||||
};
|
||||
|
||||
private handleChange = ({ dirty }: FormState) => {
|
||||
this.dirty = dirty;
|
||||
};
|
||||
|
||||
private addSubmitHook: AddSubmitHook = hook => {
|
||||
this.submitHooks.push(hook);
|
||||
return () => {
|
||||
this.submitHooks = this.submitHooks.filter(h => h !== hook);
|
||||
};
|
||||
if (dirty !== this.state.dirty) {
|
||||
this.setState({ dirty });
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<SubmitHookContextProvider value={this.addSubmitHook}>
|
||||
<Configure onChange={this.handleChange} onSave={this.handleSave}>
|
||||
{this.props.children}
|
||||
</Configure>
|
||||
</SubmitHookContextProvider>
|
||||
<>
|
||||
<NavigationWarningContainer active={this.state.dirty} />
|
||||
<SubmitHookHandler onExecute={this.handleExecute}>
|
||||
{({ onSubmit }) => (
|
||||
<Configure onChange={this.handleChange} onSubmit={onSubmit}>
|
||||
{this.props.children}
|
||||
</Configure>
|
||||
)}
|
||||
</SubmitHookHandler>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withContext(({ localeBundles }) => ({ localeBundles }))(
|
||||
withUpdateSettingsMutation(ConfigureContainer)
|
||||
);
|
||||
const enhanced = withUpdateSettingsMutation(ConfigureContainer);
|
||||
export default enhanced;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Match, Router, withRouter } from "found";
|
||||
import React from "react";
|
||||
|
||||
import { TalkContext, withContext } from "talk-framework/lib/bootstrap";
|
||||
import { getMessage } from "talk-framework/lib/i18n";
|
||||
|
||||
interface Props {
|
||||
localeBundles: TalkContext["localeBundles"];
|
||||
router: Router;
|
||||
active: boolean;
|
||||
match: Match;
|
||||
}
|
||||
|
||||
class NavigationWarningContainer extends React.Component<Props> {
|
||||
private removeTransitionHook: () => void;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const warningMessage = getMessage(
|
||||
props.localeBundles,
|
||||
"configure-unsavedInputWarning",
|
||||
"You have unsaved input. Are you sure you want to leave this page?"
|
||||
);
|
||||
|
||||
this.removeTransitionHook = props.router.addTransitionHook(
|
||||
() => (this.props.active ? warningMessage : true)
|
||||
);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.removeTransitionHook();
|
||||
}
|
||||
|
||||
public render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withContext(({ localeBundles }) => ({ localeBundles }))(
|
||||
withRouter(NavigationWarningContainer)
|
||||
);
|
||||
export default enhanced;
|
||||
+2
-3
@@ -1,6 +1,7 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import { parseBool } from "talk-framework/lib/form";
|
||||
|
||||
import { CheckBox, FormField } from "talk-ui/components";
|
||||
|
||||
@@ -14,8 +15,6 @@ interface Props {
|
||||
children: (disabledInside: boolean) => React.ReactNode;
|
||||
}
|
||||
|
||||
const bool = (v: any) => !!v;
|
||||
|
||||
const ConfigBoxWithToggleField: StatelessComponent<Props> = ({
|
||||
id,
|
||||
name,
|
||||
@@ -24,7 +23,7 @@ const ConfigBoxWithToggleField: StatelessComponent<Props> = ({
|
||||
children,
|
||||
...rest
|
||||
}) => (
|
||||
<Field name={name} type="checkbox" parse={bool}>
|
||||
<Field name={name} type="checkbox" parse={parseBool}>
|
||||
{({ input }) => (
|
||||
<ConfigBox
|
||||
{...rest}
|
||||
|
||||
+3
-4
@@ -1,11 +1,10 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import { parseBool } from "talk-framework/lib/form";
|
||||
|
||||
import { CheckBox, Flex, FormField, InputLabel } from "talk-ui/components";
|
||||
|
||||
const bool = (v: any) => !!v;
|
||||
|
||||
interface Props {
|
||||
label: React.ReactNode;
|
||||
name: string;
|
||||
@@ -20,7 +19,7 @@ const TargetFilterField: StatelessComponent<Props> = ({
|
||||
<FormField>
|
||||
<InputLabel>{label}</InputLabel>
|
||||
<Flex direction="row" itemGutter="double">
|
||||
<Field name={`${name}.admin`} type="checkbox" parse={bool}>
|
||||
<Field name={`${name}.admin`} type="checkbox" parse={parseBool}>
|
||||
{({ input, meta }) => (
|
||||
<Localized id="configure-auth-targetFilterTalkAdmin">
|
||||
<CheckBox
|
||||
@@ -35,7 +34,7 @@ const TargetFilterField: StatelessComponent<Props> = ({
|
||||
</Localized>
|
||||
)}
|
||||
</Field>
|
||||
<Field name={`${name}.stream`} type="checkbox" parse={bool}>
|
||||
<Field name={`${name}.stream`} type="checkbox" parse={parseBool}>
|
||||
{({ input }) => (
|
||||
<Localized id="configure-auth-targetFilterCommentStream">
|
||||
<CheckBox
|
||||
|
||||
+4
-4
@@ -7,15 +7,15 @@ import { graphql } from "react-relay";
|
||||
|
||||
import { AuthConfigContainer_auth as AuthData } from "talk-admin/__generated__/AuthConfigContainer_auth.graphql";
|
||||
import { TalkContext, withContext } from "talk-framework/lib/bootstrap";
|
||||
import { getMessage } from "talk-framework/lib/i18n";
|
||||
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import {
|
||||
AddSubmitHook,
|
||||
RemoveSubmitHook,
|
||||
SubmitHook,
|
||||
withSubmitHookContext,
|
||||
} from "../../../submitHook";
|
||||
} from "talk-framework/lib/form";
|
||||
import { getMessage } from "talk-framework/lib/i18n";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import AuthConfig from "../components/AuthConfig";
|
||||
|
||||
interface Props {
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ const ClosedStreamMessageConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
Write a message to appear after a story is closed for commenting.
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Field name="closedMessage">
|
||||
<Field name="closeCommenting.message">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
|
||||
+2
-2
@@ -45,7 +45,7 @@ const ClosingCommentStreamsConfig: StatelessComponent<Props> = ({
|
||||
<Localized id="configure-general-closingCommentStreams-closeCommentsAutomatically">
|
||||
<InputLabel container="legend">Close Comments Automatically</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField name="autoCloseStream" disabled={disabled} />
|
||||
<OnOffField name="closeCommenting.auto" disabled={disabled} />
|
||||
</FormField>
|
||||
<FormField container={<FieldSet />}>
|
||||
<Localized id="configure-general-closingCommentStreams-closeCommentsAfter">
|
||||
@@ -53,7 +53,7 @@ const ClosingCommentStreamsConfig: StatelessComponent<Props> = ({
|
||||
</Localized>
|
||||
|
||||
<Field
|
||||
name="closedTimeout"
|
||||
name="closeCommenting.timeout"
|
||||
validate={composeValidators(
|
||||
required,
|
||||
validateWholeNumberGreaterThan(0)
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ class ClosedStreamMessageConfigContainer extends React.Component<Props> {
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment ClosedStreamMessageConfigContainer_settings on Settings {
|
||||
closedMessage
|
||||
closeCommenting {
|
||||
message
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(ClosedStreamMessageConfigContainer);
|
||||
|
||||
+4
-2
@@ -27,8 +27,10 @@ class ClosingCommentStreamsConfigContainer extends React.Component<Props> {
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment ClosingCommentStreamsConfigContainer_settings on Settings {
|
||||
autoCloseStream
|
||||
closedTimeout
|
||||
closeCommenting {
|
||||
auto
|
||||
timeout
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(ClosingCommentStreamsConfigContainer);
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<Localized id="configure-organization-email">
|
||||
<Header
|
||||
container={
|
||||
<label htmlFor="configure-organization-organizationContactEmail" />
|
||||
<label htmlFor="configure-organization-organization.contactEmail" />
|
||||
}
|
||||
>
|
||||
Organization Email
|
||||
@@ -35,7 +35,7 @@ const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
>
|
||||
<Typography variant="detail">This E-Mail will be used</Typography>
|
||||
</Localized>
|
||||
<Field name="organizationContactEmail" validate={required}>
|
||||
<Field name="organization.contactEmail" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<Localized id="configure-organization-name">
|
||||
<Header
|
||||
container={
|
||||
<label htmlFor="configure-organization-organizationName" />
|
||||
<label htmlFor="configure-organization-organization.name" />
|
||||
}
|
||||
>
|
||||
Organization Name
|
||||
@@ -38,7 +38,7 @@ const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
community and organization members
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Field name="organizationName" validate={required}>
|
||||
<Field name="organization.name" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ class OrganizationContactEmailConfigContainer extends React.Component<Props> {
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment OrganizationContactEmailConfigContainer_settings on Settings {
|
||||
organizationContactEmail
|
||||
organization {
|
||||
contactEmail
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(OrganizationContactEmailConfigContainer);
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ class OrganizationNameConfigContainer extends React.Component<Props> {
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment OrganizationNameConfigContainer_settings on Settings {
|
||||
organizationName
|
||||
organization {
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(OrganizationNameConfigContainer);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export {
|
||||
AddSubmitHook,
|
||||
SubmitHook,
|
||||
RemoveSubmitHook,
|
||||
SubmitHookContext,
|
||||
SubmitHookContextConsumer,
|
||||
SubmitHookContextProvider,
|
||||
} from "./SubmitHookContext";
|
||||
export { default as withSubmitHookContext } from "./withSubmitHookContext";
|
||||
@@ -90,83 +90,6 @@ exports[`accepts valid email 1`] = `
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`accepts valid email confirmation 1`] = `
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
For your added security, we require users to add an email address to their accounts.
|
||||
</p>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="email"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={true}
|
||||
id="email"
|
||||
name="email"
|
||||
onChange={[Function]}
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value="hans@test.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="confirmEmail"
|
||||
>
|
||||
Confirm Email Address
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={true}
|
||||
id="confirmEmail"
|
||||
name="confirmEmail"
|
||||
onChange={[Function]}
|
||||
placeholder="Confirm Email Address"
|
||||
type="text"
|
||||
value="hans@test.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Add Email Address
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`checks for invalid email 1`] = `
|
||||
<form
|
||||
autoComplete="off"
|
||||
|
||||
@@ -1,63 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`accepts valid username 1`] = `
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Your username is an identifier that will appear on all of your comments.
|
||||
</p>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="username"
|
||||
>
|
||||
Username
|
||||
</label>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextSecondary"
|
||||
>
|
||||
You may use “_” and “.” Spaces not permitted.
|
||||
</p>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={true}
|
||||
id="username"
|
||||
name="username"
|
||||
onChange={[Function]}
|
||||
placeholder="Username"
|
||||
type="text"
|
||||
value="hans"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Create Username
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`checks for invalid username 1`] = `
|
||||
<form
|
||||
autoComplete="off"
|
||||
|
||||
@@ -92,20 +92,6 @@ it("accepts valid email", async () => {
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("accepts valid email confirmation", async () => {
|
||||
const {
|
||||
form,
|
||||
emailAddressField,
|
||||
confirmEmailAddressField,
|
||||
} = await createTestRenderer();
|
||||
emailAddressField.props.onChange({ target: { value: "hans@test.com" } });
|
||||
confirmEmailAddressField.props.onChange({
|
||||
target: { value: "hans@test.com" },
|
||||
});
|
||||
form.props.onSubmit();
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows server error", async () => {
|
||||
const email = "hans@test.com";
|
||||
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
|
||||
@@ -145,7 +131,7 @@ it("shows server error", async () => {
|
||||
it("successfully sets email", async () => {
|
||||
const email = "hans@test.com";
|
||||
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toEqual({
|
||||
expectAndFail(data.input).toEqual({
|
||||
email,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ it("shows server error", async () => {
|
||||
it("successfully sets password", async () => {
|
||||
const password = "secretpassword";
|
||||
const setPassword = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toEqual({
|
||||
expectAndFail(data.input).toEqual({
|
||||
password,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
});
|
||||
|
||||
@@ -73,13 +73,6 @@ it("checks for invalid username", async () => {
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("accepts valid username", async () => {
|
||||
const { form, usernameField } = await createTestRenderer();
|
||||
usernameField.props.onChange({ target: { value: "hans" } });
|
||||
form.props.onSubmit();
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows server error", async () => {
|
||||
const username = "hans";
|
||||
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
|
||||
@@ -111,7 +104,7 @@ it("shows server error", async () => {
|
||||
it("successfully sets username", async () => {
|
||||
const username = "hans";
|
||||
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toEqual({
|
||||
expectAndFail(data.input).toEqual({
|
||||
username,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
});
|
||||
|
||||
@@ -568,8 +568,8 @@ moderation panel.
|
||||
checked={false}
|
||||
className="RadioButton-input"
|
||||
disabled={false}
|
||||
id="autoCloseStream-true"
|
||||
name="autoCloseStream"
|
||||
id="closeCommenting.auto-true"
|
||||
name="closeCommenting.auto"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
@@ -578,7 +578,7 @@ moderation panel.
|
||||
/>
|
||||
<label
|
||||
className="RadioButton-label"
|
||||
htmlFor="autoCloseStream-true"
|
||||
htmlFor="closeCommenting.auto-true"
|
||||
>
|
||||
<span>
|
||||
On
|
||||
@@ -592,8 +592,8 @@ moderation panel.
|
||||
checked={true}
|
||||
className="RadioButton-input"
|
||||
disabled={false}
|
||||
id="autoCloseStream-false"
|
||||
name="autoCloseStream"
|
||||
id="closeCommenting.auto-false"
|
||||
name="closeCommenting.auto"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
@@ -602,7 +602,7 @@ moderation panel.
|
||||
/>
|
||||
<label
|
||||
className="RadioButton-label"
|
||||
htmlFor="autoCloseStream-false"
|
||||
htmlFor="closeCommenting.auto-false"
|
||||
>
|
||||
<span>
|
||||
Off
|
||||
@@ -632,7 +632,7 @@ moderation panel.
|
||||
autoCorrect="off"
|
||||
className="TextField-input TextField-colorRegular TextField-textAlignCenter"
|
||||
disabled={false}
|
||||
name="closedTimeout-value"
|
||||
name="closeCommenting.timeout-value"
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
spellCheck={false}
|
||||
@@ -647,7 +647,7 @@ moderation panel.
|
||||
aria-label="unit"
|
||||
className="SelectField-select DurationField-unit"
|
||||
disabled={false}
|
||||
name="closedTimeout-unit"
|
||||
name="closeCommenting.timeout-unit"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
@@ -703,7 +703,7 @@ moderation panel.
|
||||
>
|
||||
<textarea
|
||||
id="configure-general-closedStreamMessage-content"
|
||||
name="closedMessage"
|
||||
name="closeCommenting.message"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
|
||||
@@ -115,7 +115,7 @@ exports[`renders configure organization 1`] = `
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
|
||||
htmlFor="configure-organization-organizationName"
|
||||
htmlFor="configure-organization-organization.name"
|
||||
>
|
||||
Organization Name
|
||||
</label>
|
||||
@@ -133,8 +133,8 @@ exports[`renders configure organization 1`] = `
|
||||
autoCorrect="off"
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="configure-organization-organizationName"
|
||||
name="organizationName"
|
||||
id="configure-organization-organization.name"
|
||||
name="organization.name"
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
spellCheck={false}
|
||||
@@ -152,7 +152,7 @@ exports[`renders configure organization 1`] = `
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
|
||||
htmlFor="configure-organization-organizationContactEmail"
|
||||
htmlFor="configure-organization-organization.contactEmail"
|
||||
>
|
||||
Organization Email
|
||||
</label>
|
||||
@@ -173,8 +173,8 @@ status of their accounts or moderation questions.
|
||||
autoCorrect="off"
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="configure-organization-organizationContactEmail"
|
||||
name="organizationContactEmail"
|
||||
id="configure-organization-organization.contactEmail"
|
||||
name="organization.contactEmail"
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
spellCheck={false}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import mockConsole from "jest-mock-console";
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
@@ -15,16 +14,6 @@ import { settings, users } from "../fixtures";
|
||||
|
||||
beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/advanced");
|
||||
// Test might pass even when it fails with errors in the log due to:
|
||||
// https://github.com/facebook/jest/issues/3917
|
||||
// We check the console to be error free..
|
||||
mockConsole("error");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Check that there are no errors in the console.
|
||||
// tslint:disable-next-line: no-console
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
@@ -72,7 +61,7 @@ it("change custom css", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.customCSSURL).toEqual("./custom.css");
|
||||
expectAndFail(data.input.settings.customCSSURL).toEqual("./custom.css");
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
@@ -117,7 +106,7 @@ it("change permitted domains to be empty", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.domains).toEqual([]);
|
||||
expectAndFail(data.input.settings.domains).toEqual([]);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
@@ -164,7 +153,7 @@ it("change permitted domains to include more domains", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.domains).toEqual([
|
||||
expectAndFail(data.input.settings.domains).toEqual([
|
||||
"localhost:8080",
|
||||
"localhost:3000",
|
||||
]);
|
||||
|
||||
@@ -112,7 +112,7 @@ it("prevents stream lock out", async () => {
|
||||
Mutation: {
|
||||
updateSettings: createSinonStub(s =>
|
||||
s.callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.auth.integrations.local).toEqual({
|
||||
expectAndFail(data.input.settings.auth.integrations.local).toEqual({
|
||||
enabled: true,
|
||||
allowRegistration: true,
|
||||
targetFilter: {
|
||||
@@ -172,7 +172,7 @@ it("change settings", async () => {
|
||||
Query: {
|
||||
discoverOIDCConfiguration: createSinonStub(s =>
|
||||
s.callsFake((_: any, data: any) => {
|
||||
expect(data).toEqual({ issuer: "http://issuer.com" });
|
||||
expectAndFail(data).toEqual({ issuer: "http://issuer.com" });
|
||||
return {
|
||||
issuer: "http://issuer.com",
|
||||
tokenURL: "http://issuer.com/tokenURL",
|
||||
@@ -186,7 +186,9 @@ it("change settings", async () => {
|
||||
updateSettings: createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.auth.integrations.facebook).toEqual({
|
||||
expectAndFail(
|
||||
data.input.settings.auth.integrations.facebook
|
||||
).toEqual({
|
||||
enabled: true,
|
||||
allowRegistration: true,
|
||||
targetFilter: {
|
||||
@@ -204,7 +206,7 @@ it("change settings", async () => {
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.auth.integrations.oidc).toEqual({
|
||||
expectAndFail(data.input.settings.auth.integrations.oidc).toEqual({
|
||||
enabled: true,
|
||||
allowRegistration: false,
|
||||
targetFilter: {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import mockConsole from "jest-mock-console";
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
@@ -17,16 +16,6 @@ import { settings, users } from "../fixtures";
|
||||
|
||||
beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/general");
|
||||
// Test might pass even when it fails with errors in the log due to:
|
||||
// https://github.com/facebook/jest/issues/3917
|
||||
// We check the console to be error free..
|
||||
mockConsole("error");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Check that there are no errors in the console.
|
||||
// tslint:disable-next-line: no-console
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const createTestRenderer = async (
|
||||
@@ -78,7 +67,7 @@ it("change site wide commenting", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.disableCommenting).toEqual({
|
||||
expectAndFail(data.input.settings.disableCommenting).toEqual({
|
||||
enabled: true,
|
||||
message: "Closing message",
|
||||
});
|
||||
@@ -139,10 +128,12 @@ it("change community guidlines", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.communityGuidelines.content).toEqual(
|
||||
expectAndFail(data.input.settings.communityGuidelines.content).toEqual(
|
||||
"This is the community guidlines summary"
|
||||
);
|
||||
expect(data.input.settings.communityGuidelines.enabled).toEqual(true);
|
||||
expectAndFail(data.input.settings.communityGuidelines.enabled).toEqual(
|
||||
true
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
@@ -198,7 +189,7 @@ it("change closed stream message", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.closedMessage).toEqual(
|
||||
expectAndFail(data.input.settings.closeCommenting.message).toEqual(
|
||||
"The stream has been closed"
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
@@ -243,7 +234,9 @@ it("change comment editing time", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.editCommentWindowLength).toEqual(108000);
|
||||
expectAndFail(data.input.settings.editCommentWindowLength).toEqual(
|
||||
108000
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
@@ -311,7 +304,7 @@ it("change comment length limitations", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.charCount).toEqual({
|
||||
expectAndFail(data.input.settings.charCount).toEqual({
|
||||
enabled: true,
|
||||
min: null,
|
||||
max: 3000,
|
||||
@@ -403,8 +396,10 @@ it("change closing comment streams", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.autoCloseStream).toEqual(true);
|
||||
expect(data.input.settings.closedTimeout).toEqual(2592000);
|
||||
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,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import mockConsole from "jest-mock-console";
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
@@ -15,16 +14,6 @@ import { settings, users } from "../fixtures";
|
||||
|
||||
beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/moderation");
|
||||
// Test might pass even when it fails with errors in the log due to:
|
||||
// https://github.com/facebook/jest/issues/3917
|
||||
// We check the console to be error free..
|
||||
mockConsole("error");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Check that there are no errors in the console.
|
||||
// tslint:disable-next-line: no-console
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
@@ -72,7 +61,7 @@ it("change akismet settings", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.integrations.akismet).toEqual({
|
||||
expectAndFail(data.input.settings.integrations.akismet).toEqual({
|
||||
enabled: true,
|
||||
key: "my api key",
|
||||
site: "https://coralproject.net",
|
||||
@@ -156,7 +145,7 @@ it("change perspective settings", async () => {
|
||||
const updateSettingsStub = createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.integrations.perspective).toEqual({
|
||||
expectAndFail(data.input.settings.integrations.perspective).toEqual({
|
||||
doNotStore: false,
|
||||
enabled: true,
|
||||
endpoint: "https://custom-endpoint.net",
|
||||
@@ -171,7 +160,7 @@ it("change perspective settings", async () => {
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_: any, data: any) => {
|
||||
expect(
|
||||
expectAndFail(
|
||||
data.input.settings.integrations.perspective.threshold
|
||||
).toBeNull();
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import mockConsole from "jest-mock-console";
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
@@ -15,16 +14,6 @@ import { settings, users } from "../fixtures";
|
||||
|
||||
beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/organization");
|
||||
// Test might pass even when it fails with errors in the log due to:
|
||||
// https://github.com/facebook/jest/issues/3917
|
||||
// We check the console to be error free..
|
||||
mockConsole("error");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Check that there are no errors in the console.
|
||||
// tslint:disable-next-line: no-console
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
@@ -72,7 +61,9 @@ it("change organization name", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.organizationName).toEqual("Coral Test");
|
||||
expectAndFail(data.input.settings.organization.name).toEqual(
|
||||
"Coral Test"
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
@@ -135,7 +126,7 @@ it("change organization contact email", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.organizationContactEmail).toEqual(
|
||||
expectAndFail(data.input.settings.organization.contactEmail).toEqual(
|
||||
"test@coralproject.net"
|
||||
);
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import mockConsole from "jest-mock-console";
|
||||
import { cloneDeep, get, merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
@@ -15,16 +14,6 @@ import { settings, users } from "../fixtures";
|
||||
|
||||
beforeEach(() => {
|
||||
replaceHistoryLocation("http://localhost/admin/configure/wordList");
|
||||
// Test might pass even when it fails with errors in the log due to:
|
||||
// https://github.com/facebook/jest/issues/3917
|
||||
// We check the console to be error free..
|
||||
mockConsole("error");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Check that there are no errors in the console.
|
||||
// tslint:disable-next-line: no-console
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
@@ -72,7 +61,7 @@ it("change banned and suspect words", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.wordList).toEqual({
|
||||
expectAndFail(data.input.settings.wordList).toEqual({
|
||||
banned: ["Fuck", "Asshole"],
|
||||
suspect: ["idiot", "shame"],
|
||||
});
|
||||
|
||||
@@ -14,9 +14,10 @@ export const settings = {
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
},
|
||||
closedTimeout: 604800,
|
||||
autoCloseStream: false,
|
||||
closedMessage: null,
|
||||
closeCommenting: {
|
||||
auto: false,
|
||||
timeout: 604800,
|
||||
},
|
||||
customCSSURL: null,
|
||||
domains: ["localhost:8080"],
|
||||
editCommentWindowLength: 30000,
|
||||
@@ -24,8 +25,11 @@ export const settings = {
|
||||
enabled: false,
|
||||
content: "",
|
||||
},
|
||||
organizationContactEmail: "coral@test.com",
|
||||
organizationName: "Coral",
|
||||
organization: {
|
||||
name: "Coral",
|
||||
url: "https://test.com/",
|
||||
contactEmail: "coral@test.com",
|
||||
},
|
||||
integrations: {
|
||||
akismet: {
|
||||
enabled: false,
|
||||
|
||||
@@ -83,7 +83,7 @@ describe("reported queue", () => {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: sinon.stub().callsFake(data => {
|
||||
expect(data).toEqual({ first: 5 });
|
||||
expectAndFail(data).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
@@ -119,7 +119,7 @@ describe("reported queue", () => {
|
||||
comments: createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake(data => {
|
||||
expect(data).toEqual({ first: 5 });
|
||||
expectAndFail(data).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
@@ -139,7 +139,7 @@ describe("reported queue", () => {
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake(data => {
|
||||
expect(data).toEqual({
|
||||
expectAndFail(data).toEqual({
|
||||
first: 10,
|
||||
after: reportedComments[1].createdAt,
|
||||
});
|
||||
@@ -195,7 +195,7 @@ describe("reported queue", () => {
|
||||
|
||||
it("accepts comment in reported queue", async () => {
|
||||
const acceptCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: reportedComments[0].id,
|
||||
commentRevisionID: reportedComments[0].revision.id,
|
||||
@@ -221,7 +221,7 @@ describe("reported queue", () => {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: sinon.stub().callsFake(data => {
|
||||
expect(data).toEqual({ first: 5 });
|
||||
expectAndFail(data).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
@@ -272,7 +272,7 @@ describe("reported queue", () => {
|
||||
|
||||
it("rejects comment in reported queue", async () => {
|
||||
const rejectCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: reportedComments[0].id,
|
||||
commentRevisionID: reportedComments[0].revision.id,
|
||||
@@ -298,7 +298,7 @@ describe("reported queue", () => {
|
||||
reported: {
|
||||
count: 2,
|
||||
comments: sinon.stub().callsFake(data => {
|
||||
expect(data).toEqual({ first: 5 });
|
||||
expectAndFail(data).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
@@ -357,7 +357,10 @@ describe("rejected queue", () => {
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
comments: sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toEqual({ first: 5, status: "REJECTED" });
|
||||
expectAndFail(data).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
@@ -388,7 +391,7 @@ describe("rejected queue", () => {
|
||||
comments: createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake((_, data) => {
|
||||
expect(data).toEqual({
|
||||
expectAndFail(data).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
});
|
||||
@@ -411,7 +414,7 @@ describe("rejected queue", () => {
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((_, data) => {
|
||||
expect(data).toEqual({
|
||||
expectAndFail(data).toEqual({
|
||||
first: 10,
|
||||
after: rejectedComments[1].createdAt,
|
||||
status: "REJECTED",
|
||||
@@ -467,7 +470,7 @@ describe("rejected queue", () => {
|
||||
|
||||
it("accepts comment in rejected queue", async () => {
|
||||
const acceptCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: rejectedComments[0].id,
|
||||
commentRevisionID: rejectedComments[0].revision.id,
|
||||
@@ -490,7 +493,10 @@ describe("rejected queue", () => {
|
||||
const testRenderer = await createTestRenderer({
|
||||
Query: {
|
||||
comments: sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toEqual({ first: 5, status: "REJECTED" });
|
||||
expectAndFail(data).toEqual({
|
||||
first: 5,
|
||||
status: "REJECTED",
|
||||
});
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
@@ -541,7 +547,7 @@ describe("rejected queue", () => {
|
||||
describe("single comment view", () => {
|
||||
const comment = rejectedComments[0];
|
||||
const commentStub = sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toEqual({ id: comment.id });
|
||||
expectAndFail(data).toEqual({ id: comment.id });
|
||||
return reportedComments[0];
|
||||
});
|
||||
|
||||
@@ -566,7 +572,7 @@ describe("single comment view", () => {
|
||||
|
||||
it("accepts single comment", async () => {
|
||||
const acceptCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
@@ -604,7 +610,7 @@ describe("single comment view", () => {
|
||||
|
||||
it("rejects single comment", async () => {
|
||||
const rejectCommentStub = sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: comment.id,
|
||||
commentRevisionID: comment.revision.id,
|
||||
|
||||
@@ -159,152 +159,6 @@ Your email address will be used to:
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`accepts valid email confirmation 1`] = `
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
For your added security, we require users to add an email address to their accounts.
|
||||
Your email address will be used to:
|
||||
</p>
|
||||
<ul
|
||||
className="UnorderedList-root"
|
||||
>
|
||||
<li
|
||||
className="ListItem-root"
|
||||
>
|
||||
<div
|
||||
className="ListItem-leftCol"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Receive updates regarding any changes to your account
|
||||
(email address, username, password, etc.)
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
className="ListItem-root"
|
||||
>
|
||||
<div
|
||||
className="ListItem-leftCol"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Allow you to download your comments.
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
className="ListItem-root"
|
||||
>
|
||||
<div
|
||||
className="ListItem-leftCol"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
done
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Send comment notifications that you have chosen to receive.
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="email"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={true}
|
||||
id="email"
|
||||
name="email"
|
||||
onChange={[Function]}
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value="hans@test.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="confirmEmail"
|
||||
>
|
||||
Confirm Email Address
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={true}
|
||||
id="confirmEmail"
|
||||
name="confirmEmail"
|
||||
onChange={[Function]}
|
||||
placeholder="Confirm Email Address"
|
||||
type="text"
|
||||
value="hans@test.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Add Email Address
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`checks for invalid email 1`] = `
|
||||
<form
|
||||
autoComplete="off"
|
||||
|
||||
@@ -1,63 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`accepts valid username 1`] = `
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Your username is an identifier that will appear on all of your comments.
|
||||
</p>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="username"
|
||||
>
|
||||
Username
|
||||
</label>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextSecondary"
|
||||
>
|
||||
You may use “_” and “.” Spaces not permitted.
|
||||
</p>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={true}
|
||||
id="username"
|
||||
name="username"
|
||||
onChange={[Function]}
|
||||
placeholder="Username"
|
||||
type="text"
|
||||
value="hans"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Create Username
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`checks for invalid username 1`] = `
|
||||
<form
|
||||
autoComplete="off"
|
||||
|
||||
@@ -100,20 +100,6 @@ it("accepts valid email", async () => {
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("accepts valid email confirmation", async () => {
|
||||
const {
|
||||
form,
|
||||
emailAddressField,
|
||||
confirmEmailAddressField,
|
||||
} = await createTestRenderer();
|
||||
emailAddressField.props.onChange({ target: { value: "hans@test.com" } });
|
||||
confirmEmailAddressField.props.onChange({
|
||||
target: { value: "hans@test.com" },
|
||||
});
|
||||
form.props.onSubmit();
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows server error", async () => {
|
||||
const email = "hans@test.com";
|
||||
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
|
||||
@@ -153,7 +139,7 @@ it("shows server error", async () => {
|
||||
it("successfully sets email", async () => {
|
||||
const email = "hans@test.com";
|
||||
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toEqual({
|
||||
expectAndFail(data.input).toEqual({
|
||||
email,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
});
|
||||
|
||||
@@ -113,7 +113,7 @@ it("shows server error", async () => {
|
||||
it("successfully sets password", async () => {
|
||||
const password = "secretpassword";
|
||||
const setPassword = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toEqual({
|
||||
expectAndFail(data.input).toEqual({
|
||||
password,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
});
|
||||
|
||||
@@ -82,13 +82,6 @@ it("checks for invalid username", async () => {
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("accepts valid username", async () => {
|
||||
const { form, usernameField } = await createTestRenderer();
|
||||
usernameField.props.onChange({ target: { value: "hans" } });
|
||||
form.props.onSubmit();
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows server error", async () => {
|
||||
const username = "hans";
|
||||
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
|
||||
@@ -120,7 +113,7 @@ it("shows server error", async () => {
|
||||
it("successfully sets username", async () => {
|
||||
const username = "hans";
|
||||
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toEqual({
|
||||
expectAndFail(data.input).toEqual({
|
||||
username,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
export default function getStory(environment: Environment, id: string) {
|
||||
return environment
|
||||
.getStore()
|
||||
.getSource()
|
||||
.get(id);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import getStory from "./getStory";
|
||||
|
||||
export default function getStorySettings(environment: Environment, id: string) {
|
||||
const story = getStory(environment, id);
|
||||
if (!story) {
|
||||
return null;
|
||||
}
|
||||
const storySettingsRef = story.settings.__ref;
|
||||
return environment
|
||||
.getStore()
|
||||
.getSource()
|
||||
.get(storySettingsRef);
|
||||
}
|
||||
@@ -7,3 +7,5 @@ export { default as redirectOAuth2 } from "./redirectOAuth2";
|
||||
export {
|
||||
default as getParamsFromHashAndClearIt,
|
||||
} from "./getParamsFromHashAndClearIt";
|
||||
export { default as getStory } from "./getStory";
|
||||
export { default as getStorySettings } from "./getStorySettings";
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
form: FormApi;
|
||||
rootKey?: string;
|
||||
children: (
|
||||
params: {
|
||||
onInitValues: (data: any) => void;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* FormInitializer exposes `onInitValues` property as a render prop.
|
||||
* This prop can be called multiple times until `FormInitializer` will
|
||||
* run `form.initizalize` during mount.
|
||||
*
|
||||
* This is useful if you're initialization data is spread across components.
|
||||
*/
|
||||
class FormInitializer extends React.Component<Props> {
|
||||
private initialValues: any = {};
|
||||
|
||||
public componentDidMount() {
|
||||
let values = this.initialValues;
|
||||
if (this.props.rootKey) {
|
||||
values = { [this.props.rootKey]: values };
|
||||
}
|
||||
this.props.form.initialize(values);
|
||||
}
|
||||
|
||||
private handlePartialInit = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
return this.props.children({
|
||||
onInitValues: this.handlePartialInit,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default FormInitializer;
|
||||
@@ -0,0 +1,85 @@
|
||||
import { FormApi } from "final-form";
|
||||
import React from "react";
|
||||
|
||||
import { InvalidRequestError } from "talk-framework/lib/errors";
|
||||
|
||||
import { AddSubmitHook, SubmitHook, SubmitHookContextProvider } from "./";
|
||||
|
||||
interface Props {
|
||||
onExecute: (data: any, form: FormApi) => Promise<void>;
|
||||
children: (
|
||||
params: {
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* SubmitHookContainer provides the SubmitHook Context and will
|
||||
* run all hooks when calling the `onSubmit` render prop.
|
||||
* If the submit was not cancelled, `onExecute` will be called in
|
||||
* the end, e.g. to call the mutation with the final data.
|
||||
*/
|
||||
class SubmitHookContainer extends React.Component<Props> {
|
||||
private submitHooks: SubmitHook[] = [];
|
||||
|
||||
private handleSave = async (data: any, form: FormApi) => {
|
||||
let cancelled = false;
|
||||
let formErrors: Record<string, React.ReactNode> = {};
|
||||
const executeCallbacks: Array<() => Promise<any>> = [];
|
||||
const cancel = (errors: Record<string, React.ReactNode>) => {
|
||||
cancelled = true;
|
||||
formErrors = { ...errors, ...formErrors };
|
||||
};
|
||||
const onExecute = (cb: () => Promise<any>) => {
|
||||
executeCallbacks.push(cb);
|
||||
};
|
||||
try {
|
||||
// Call submit hooks, that can manipulate what
|
||||
// we send as the mutation.
|
||||
let nextData = data;
|
||||
for (const hook of this.submitHooks) {
|
||||
const result = await hook(nextData, { cancel, onExecute });
|
||||
if (result) {
|
||||
nextData = result;
|
||||
}
|
||||
}
|
||||
if (cancelled) {
|
||||
return formErrors;
|
||||
}
|
||||
|
||||
executeCallbacks.push(() => this.props.onExecute(nextData, form));
|
||||
for (const c of executeCallbacks.map(cb => cb())) {
|
||||
await c;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidRequestError) {
|
||||
return error.invalidArgs;
|
||||
}
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
private addSubmitHook: AddSubmitHook = hook => {
|
||||
this.submitHooks.push(hook);
|
||||
return () => {
|
||||
this.submitHooks = this.submitHooks.filter(h => h !== hook);
|
||||
};
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<SubmitHookContextProvider value={this.addSubmitHook}>
|
||||
{this.props.children({
|
||||
onSubmit: this.handleSave,
|
||||
})}
|
||||
</SubmitHookContextProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SubmitHookContainer;
|
||||
+1
@@ -44,6 +44,7 @@ export const formatPercentage = (v: any) => {
|
||||
return Math.round(v * 100).toString();
|
||||
};
|
||||
|
||||
export const parseBool = (v: any) => Boolean(v);
|
||||
export const parseStringBool = (v: string) => v === "true";
|
||||
|
||||
export const parseNewLineDelimitedString = (v: string) => v.split("\n");
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./helpers";
|
||||
export * from "./SubmitHookContext";
|
||||
export { default as withSubmitHookContext } from "./withSubmitHookContext";
|
||||
export { default as SubmitHookHandler } from "./SubmitHookHandler";
|
||||
export { default as FormInitializer } from "./FormInitializer";
|
||||
@@ -24,11 +24,7 @@ export async function commitMutationPromiseNormalized<T extends OperationBase>(
|
||||
environment: Environment,
|
||||
config: MutationPromiseConfig<T>
|
||||
): Promise<T["response"][keyof T["response"]]> {
|
||||
try {
|
||||
return await commitMutationPromise(environment, config);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
return await commitMutationPromise(environment, config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,11 @@ import { RestClient } from "../lib/rest";
|
||||
|
||||
export interface InstallInput {
|
||||
tenant: {
|
||||
organizationName: string;
|
||||
organizationContactEmail: string;
|
||||
organizationURL: string;
|
||||
organization: {
|
||||
name: string;
|
||||
contactEmail: string;
|
||||
url: string;
|
||||
};
|
||||
domains: string[];
|
||||
};
|
||||
user: {
|
||||
|
||||
@@ -39,9 +39,11 @@ function shapeFinalData(data: FormData): InstallInput {
|
||||
|
||||
return {
|
||||
tenant: {
|
||||
organizationName,
|
||||
organizationContactEmail,
|
||||
organizationURL,
|
||||
organization: {
|
||||
name: organizationName,
|
||||
contactEmail: organizationContactEmail,
|
||||
url: organizationURL,
|
||||
},
|
||||
domains,
|
||||
},
|
||||
user: {
|
||||
|
||||
@@ -11,11 +11,7 @@ export async function commit(
|
||||
input: InstallInput,
|
||||
{ rest }: TalkContext
|
||||
) {
|
||||
try {
|
||||
await install(rest, input);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
await install(rest, input);
|
||||
}
|
||||
|
||||
export const withInstallMutation = createMutationContainer("install", commit);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { FORM_ERROR } from "final-form";
|
||||
import React, { Component } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
@@ -18,12 +17,8 @@ class AddOrganizationContainer extends Component<
|
||||
AddOrganizationContainerProps
|
||||
> {
|
||||
private onSubmit: AddOrganizationForm["onSubmit"] = async (input, form) => {
|
||||
try {
|
||||
this.props.onSaveData(input);
|
||||
return this.props.onGoToNextStep();
|
||||
} catch (error) {
|
||||
return { [FORM_ERROR]: error.message };
|
||||
}
|
||||
this.props.onSaveData(input);
|
||||
return this.props.onGoToNextStep();
|
||||
};
|
||||
public render() {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { FORM_ERROR } from "final-form";
|
||||
import React, { Component } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
@@ -18,12 +17,8 @@ class CreateYourAccountContainer extends Component<
|
||||
CreateYourAccountContainerProps
|
||||
> {
|
||||
private onSubmit: CreateYourAccountForm["onSubmit"] = async (input, form) => {
|
||||
try {
|
||||
this.props.onSaveData(input);
|
||||
return this.props.onGoToNextStep();
|
||||
} catch (error) {
|
||||
return { [FORM_ERROR]: error.message };
|
||||
}
|
||||
this.props.onSaveData(input);
|
||||
return this.props.onGoToNextStep();
|
||||
};
|
||||
public render() {
|
||||
return (
|
||||
|
||||
@@ -4,6 +4,7 @@ import TabBarQuery from "talk-stream/queries/TabBarQuery";
|
||||
import { HorizontalGutter, TabContent, TabPane } from "talk-ui/components";
|
||||
|
||||
import CommentsPaneContainer from "../tabs/comments/containers/CommentsPaneContainer";
|
||||
import ConfigureQuery from "../tabs/configure/queries/ConfigureQuery";
|
||||
import ProfileQuery from "../tabs/profile/queries/ProfileQuery";
|
||||
|
||||
import styles from "./App.css";
|
||||
@@ -25,6 +26,9 @@ const App: StatelessComponent<AppProps> = props => {
|
||||
<TabPane tabId="PROFILE" data-testid="current-tab-pane">
|
||||
<ProfileQuery />
|
||||
</TabPane>
|
||||
<TabPane tabId="CONFIGURE" data-testid="current-tab-pane">
|
||||
<ConfigureQuery />
|
||||
</TabPane>
|
||||
</TabContent>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { Tab, TabBar } from "talk-ui/components";
|
||||
import { Icon, MatchMedia, Tab, TabBar } from "talk-ui/components";
|
||||
|
||||
type TabValue = "COMMENTS" | "PROFILE" | "%future added value";
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface Props {
|
||||
onTabClick: (tab: TabValue) => void;
|
||||
commentCount: number;
|
||||
showProfileTab: boolean;
|
||||
showConfigureTab: boolean;
|
||||
}
|
||||
|
||||
const AppTabBar: StatelessComponent<Props> = props => {
|
||||
@@ -30,6 +31,21 @@ const AppTabBar: StatelessComponent<Props> = props => {
|
||||
</Localized>
|
||||
</Tab>
|
||||
)}
|
||||
{props.showConfigureTab && (
|
||||
<Tab tabId="CONFIGURE">
|
||||
<MatchMedia gteWidth="sm">
|
||||
{matches =>
|
||||
matches ? (
|
||||
<Localized id="general-tabBar-configure">
|
||||
<span>Configure</span>
|
||||
</Localized>
|
||||
) : (
|
||||
<Icon aria-label="Configure">settings</Icon>
|
||||
)
|
||||
}
|
||||
</MatchMedia>
|
||||
</Tab>
|
||||
)}
|
||||
</TabBar>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,6 +21,12 @@ exports[`renders correctly 1`] = `
|
||||
>
|
||||
<withContext(withLocalStateContainer(ProfileQuery)) />
|
||||
</TabPane>
|
||||
<TabPane
|
||||
data-testid="current-tab-pane"
|
||||
tabId="CONFIGURE"
|
||||
>
|
||||
<withContext(withLocalStateContainer(ConfigureQuery)) />
|
||||
</TabPane>
|
||||
</TabContent>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
withLocalStateContainer,
|
||||
} from "talk-framework/lib/relay";
|
||||
|
||||
import { TabBarContainer_me as MeData } from "talk-stream/__generated__/TabBarContainer_me.graphql";
|
||||
import { TabBarContainer_story as StoryData } from "talk-stream/__generated__/TabBarContainer_story.graphql";
|
||||
import { TabBarContainerLocal as Local } from "talk-stream/__generated__/TabBarContainerLocal.graphql";
|
||||
import { roleIsAtLeast } from "talk-stream/helpers";
|
||||
import {
|
||||
SetActiveTabInput,
|
||||
SetActiveTabMutation,
|
||||
@@ -19,6 +21,7 @@ import TabBar from "../components/TabBar";
|
||||
|
||||
interface Props {
|
||||
story: StoryData | null;
|
||||
me: MeData | null;
|
||||
local: Local;
|
||||
setActiveTab: SetActiveTabMutation;
|
||||
}
|
||||
@@ -40,6 +43,9 @@ export class TabBarContainer extends Component<Props> {
|
||||
activeTab={activeTab}
|
||||
commentCount={commentCount}
|
||||
showProfileTab={loggedIn}
|
||||
showConfigureTab={
|
||||
!!this.props.me && roleIsAtLeast(this.props.me.role, "MODERATOR")
|
||||
}
|
||||
onTabClick={this.handleSetActiveTab}
|
||||
/>
|
||||
);
|
||||
@@ -56,6 +62,11 @@ const enhanced = withSetActiveTabMutation(
|
||||
`
|
||||
)(
|
||||
withFragmentContainer<Props>({
|
||||
me: graphql`
|
||||
fragment TabBarContainer_me on User {
|
||||
role
|
||||
}
|
||||
`,
|
||||
story: graphql`
|
||||
fragment TabBarContainer_story on Story {
|
||||
commentCounts {
|
||||
|
||||
@@ -7,4 +7,4 @@ export {
|
||||
export { default as isVisible } from "./isVisible";
|
||||
export { default as isInReview } from "./isInReview";
|
||||
export { default as isRejected } from "./isRejected";
|
||||
export { default as isRolePriviledged } from "./isRolePriviledged";
|
||||
export { default as roleIsAtLeast } from "./roleIsAtLeast";
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// TODO: use generated schema types.
|
||||
const priviledgedRoles = ["ADMIN", "MODERATOR"];
|
||||
export default function isRolePriviledged(role: any) {
|
||||
return priviledgedRoles.includes(role);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// TODO: use generated schema types.
|
||||
type Role =
|
||||
| "ADMIN"
|
||||
| "MODERATOR"
|
||||
| "STAFF"
|
||||
| "COMMENTER"
|
||||
| "%future added value";
|
||||
|
||||
const hierarchy: Role[] = ["COMMENTER", "STAFF", "MODERATOR", "ADMIN"];
|
||||
export default function roleIsAtLeast(role: Role, atLeast: Role) {
|
||||
[role, atLeast].forEach(r => {
|
||||
if (!hierarchy.includes(r)) {
|
||||
throw new Error(`Unknown role ${r}`);
|
||||
}
|
||||
});
|
||||
return hierarchy.indexOf(atLeast) <= hierarchy.indexOf(role);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
RecordSourceSelectorProxy,
|
||||
} from "relay-runtime";
|
||||
|
||||
import { getMe } from "talk-framework/helpers";
|
||||
import { getMe, getStorySettings } from "talk-framework/helpers";
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
@@ -18,9 +18,9 @@ import { CreateCommentMutation as MutationTypes } from "talk-stream/__generated_
|
||||
|
||||
import {
|
||||
incrementStoryCommentCounts,
|
||||
isRolePriviledged,
|
||||
isVisible,
|
||||
prependCommentEdgeToProfile,
|
||||
roleIsAtLeast,
|
||||
} from "../helpers";
|
||||
|
||||
export type CreateCommentInput = MutationInput<MutationTypes>;
|
||||
@@ -74,7 +74,9 @@ function addCommentToStory(
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
graphql`
|
||||
fragment CreateCommentMutation_story on Story {
|
||||
moderation
|
||||
settings {
|
||||
moderation
|
||||
}
|
||||
}
|
||||
fragment CreateCommentMutation_me on User {
|
||||
role
|
||||
@@ -108,17 +110,14 @@ function commit(
|
||||
const currentDate = new Date().toISOString();
|
||||
const id = uuidGenerator();
|
||||
|
||||
const story = relayEnvironment
|
||||
.getStore()
|
||||
.getSource()
|
||||
.get(input.storyID);
|
||||
if (!story || !story.moderation) {
|
||||
const storySettings = getStorySettings(relayEnvironment, input.storyID);
|
||||
if (!storySettings || !storySettings.moderation) {
|
||||
throw new Error("Moderation mode of the story was not included");
|
||||
}
|
||||
|
||||
// TODO: Generate and use schema types.
|
||||
const expectPremoderation =
|
||||
!isRolePriviledged(me.role) && story.moderation === "PRE";
|
||||
!roleIsAtLeast(me.role, "STAFF") && storySettings.moderation === "PRE";
|
||||
|
||||
return commitMutationPromiseNormalized<MutationTypes>(environment, {
|
||||
mutation,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
RecordSourceSelectorProxy,
|
||||
} from "relay-runtime";
|
||||
|
||||
import { getMe } from "talk-framework/helpers";
|
||||
import { getMe, getStorySettings } from "talk-framework/helpers";
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
@@ -18,9 +18,9 @@ import { CreateCommentReplyMutation as MutationTypes } from "talk-stream/__gener
|
||||
|
||||
import {
|
||||
incrementStoryCommentCounts,
|
||||
isRolePriviledged,
|
||||
isVisible,
|
||||
prependCommentEdgeToProfile,
|
||||
roleIsAtLeast,
|
||||
} from "../helpers";
|
||||
|
||||
export type CreateCommentReplyInput = MutationInput<MutationTypes> & {
|
||||
@@ -102,7 +102,9 @@ function addLocalCommentReplyToStory(
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
graphql`
|
||||
fragment CreateCommentReplyMutation_story on Story {
|
||||
moderation
|
||||
settings {
|
||||
moderation
|
||||
}
|
||||
}
|
||||
fragment CreateCommentReplyMutation_me on User {
|
||||
role
|
||||
@@ -135,17 +137,14 @@ function commit(
|
||||
const currentDate = new Date().toISOString();
|
||||
const id = uuidGenerator();
|
||||
|
||||
const story = relayEnvironment
|
||||
.getStore()
|
||||
.getSource()
|
||||
.get(input.storyID);
|
||||
if (!story || !story.moderation) {
|
||||
const storySettings = getStorySettings(relayEnvironment, input.storyID);
|
||||
if (!storySettings || !storySettings.moderation) {
|
||||
throw new Error("Moderation mode of the story was not included");
|
||||
}
|
||||
|
||||
// TODO: Generate and use schema types.
|
||||
const expectPremoderation =
|
||||
!isRolePriviledged(me.role) && story.moderation === "PRE";
|
||||
!roleIsAtLeast(me.role, "STAFF") && storySettings.moderation === "PRE";
|
||||
|
||||
return commitMutationPromiseNormalized<MutationTypes>(environment, {
|
||||
mutation,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { graphql } from "react-relay";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
createMutationContainer,
|
||||
MutationInput,
|
||||
MutationResponsePromise,
|
||||
} from "talk-framework/lib/relay";
|
||||
|
||||
import { UpdateStorySettingsMutation as MutationTypes } from "talk-stream/__generated__/UpdateStorySettingsMutation.graphql";
|
||||
|
||||
export type UpdateStorySettingsInput = MutationInput<MutationTypes>;
|
||||
|
||||
const mutation = graphql`
|
||||
mutation UpdateStorySettingsMutation($input: UpdateStorySettingsInput!) {
|
||||
updateStorySettings(input: $input) {
|
||||
story {
|
||||
...ConfigureContainer_story
|
||||
}
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
let clientMutationId = 0;
|
||||
|
||||
function commit(environment: Environment, input: UpdateStorySettingsInput) {
|
||||
return commitMutationPromiseNormalized<MutationTypes>(environment, {
|
||||
mutation,
|
||||
variables: {
|
||||
input: {
|
||||
...input,
|
||||
clientMutationId: (clientMutationId++).toString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const withUpdateStorySettingsMutation = createMutationContainer(
|
||||
"updateStorySettings",
|
||||
commit
|
||||
);
|
||||
|
||||
export type UpdateStorySettingsMutation = (
|
||||
input: UpdateStorySettingsInput
|
||||
) => MutationResponsePromise<MutationTypes, "updateStorySettings">;
|
||||
@@ -55,3 +55,8 @@ export {
|
||||
withSetStoryClosedMutation,
|
||||
SetStoryClosedMutation,
|
||||
} from "./SetStoryClosedMutation";
|
||||
export {
|
||||
UpdateStorySettingsInput,
|
||||
withUpdateStorySettingsMutation,
|
||||
UpdateStorySettingsMutation,
|
||||
} from "./UpdateStorySettingsMutation";
|
||||
|
||||
@@ -21,6 +21,9 @@ class TabBarQuery extends Component<Props> {
|
||||
<QueryRenderer<QueryTypes>
|
||||
query={graphql`
|
||||
query TabBarQuery($storyID: ID, $storyURL: String) {
|
||||
me {
|
||||
...TabBarContainer_me
|
||||
}
|
||||
story(id: $storyID, url: $storyURL) {
|
||||
...TabBarContainer_story
|
||||
}
|
||||
@@ -35,7 +38,12 @@ class TabBarQuery extends Component<Props> {
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
|
||||
return <TabBarContainer story={(props && props.story) || null} />;
|
||||
return (
|
||||
<TabBarContainer
|
||||
me={(props && props.me) || null}
|
||||
story={(props && props.story) || null}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,10 @@ import styles from "./RTE.css";
|
||||
const Localized = React.forwardRef<any, PropTypesOf<typeof LocalizedOriginal>>(
|
||||
({ api, ...props }, ref) => (
|
||||
<LocalizedOriginal {...props}>
|
||||
{React.cloneElement(React.Children.only(props.children), { api, ref })}
|
||||
{React.cloneElement(
|
||||
React.Children.only(props.children as React.ReactElement),
|
||||
{ api, ref }
|
||||
)}
|
||||
</LocalizedOriginal>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -35,7 +35,9 @@ function createDefaultProps(add: DeepPartial<Props> = {}): Props {
|
||||
min: 3,
|
||||
max: 100,
|
||||
},
|
||||
closedMessage: "closed",
|
||||
closeCommenting: {
|
||||
message: "closed",
|
||||
},
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
message: "",
|
||||
@@ -124,7 +126,9 @@ it("renders when story has been closed (collapsing)", async () => {
|
||||
isClosed: true,
|
||||
},
|
||||
settings: {
|
||||
closedMessage: "story closed",
|
||||
closeCommenting: {
|
||||
message: "story closed",
|
||||
},
|
||||
},
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
@@ -154,7 +158,9 @@ it("renders when story has been closed (non-collapsing)", async () => {
|
||||
isClosed: false,
|
||||
},
|
||||
settings: {
|
||||
closedMessage: "story closed",
|
||||
closeCommenting: {
|
||||
message: "story closed",
|
||||
},
|
||||
},
|
||||
});
|
||||
const nextProps = createDefaultProps({
|
||||
@@ -162,7 +168,9 @@ it("renders when story has been closed (non-collapsing)", async () => {
|
||||
isClosed: true,
|
||||
},
|
||||
settings: {
|
||||
closedMessage: "story closed",
|
||||
closeCommenting: {
|
||||
message: "story closed",
|
||||
},
|
||||
},
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
|
||||
@@ -137,7 +137,7 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
closedMessage={
|
||||
(this.props.settings.disableCommenting.enabled &&
|
||||
this.props.settings.disableCommenting.message) ||
|
||||
this.props.settings.closedMessage
|
||||
this.props.settings.closeCommenting.message
|
||||
}
|
||||
/>
|
||||
);
|
||||
@@ -167,7 +167,7 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
disabledMessage={
|
||||
(this.props.settings.disableCommenting.enabled &&
|
||||
this.props.settings.disableCommenting.message) ||
|
||||
this.props.settings.closedMessage
|
||||
this.props.settings.closeCommenting.message
|
||||
}
|
||||
submitStatus={this.state.submitStatus}
|
||||
/>
|
||||
@@ -199,7 +199,9 @@ const enhanced = withContext(({ sessionStorage }) => ({
|
||||
enabled
|
||||
message
|
||||
}
|
||||
closedMessage
|
||||
closeCommenting {
|
||||
message
|
||||
}
|
||||
}
|
||||
`,
|
||||
story: graphql`
|
||||
|
||||
@@ -26,6 +26,7 @@ function createDefaultProps(add: DeepPartial<Props> = {}): Props {
|
||||
{
|
||||
createCommentReply: noop as any,
|
||||
refreshSettings: noop as any,
|
||||
onClose: noop as any,
|
||||
story: {
|
||||
id: "story-id",
|
||||
isClosed: false,
|
||||
@@ -47,7 +48,9 @@ function createDefaultProps(add: DeepPartial<Props> = {}): Props {
|
||||
min: 3,
|
||||
max: 100,
|
||||
},
|
||||
closedMessage: "closed",
|
||||
closeCommenting: {
|
||||
message: "closed",
|
||||
},
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
message: "",
|
||||
@@ -186,7 +189,9 @@ it("renders when story has been closed", async () => {
|
||||
isClosed: true,
|
||||
},
|
||||
settings: {
|
||||
closedMessage: "story closed",
|
||||
closeCommenting: {
|
||||
message: "story closed",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ export class ReplyCommentFormContainer extends Component<Props, State> {
|
||||
disabledMessage={
|
||||
(this.props.settings.disableCommenting.enabled &&
|
||||
this.props.settings.disableCommenting.message) ||
|
||||
this.props.settings.closedMessage
|
||||
this.props.settings.closeCommenting.message
|
||||
}
|
||||
/>
|
||||
);
|
||||
@@ -195,7 +195,9 @@ const enhanced = withContext(({ sessionStorage, browserInfo }) => ({
|
||||
enabled
|
||||
message
|
||||
}
|
||||
closedMessage
|
||||
closeCommenting {
|
||||
message
|
||||
}
|
||||
}
|
||||
`,
|
||||
story: graphql`
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import UserBoxContainer from "talk-stream/containers/UserBoxContainer";
|
||||
import { HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import ConfigureCommentStreamContainer from "../containers/ConfigureCommentStreamContainer";
|
||||
|
||||
export interface Props {
|
||||
me: PropTypesOf<typeof UserBoxContainer>["me"];
|
||||
settings: PropTypesOf<typeof UserBoxContainer>["settings"];
|
||||
story: PropTypesOf<typeof ConfigureCommentStreamContainer>["story"];
|
||||
}
|
||||
|
||||
const Configure: StatelessComponent<Props> = props => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<UserBoxContainer me={props.me} settings={props.settings} />
|
||||
<ConfigureCommentStreamContainer story={props.story} />
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
export default Configure;
|
||||
@@ -0,0 +1,3 @@
|
||||
.heading {
|
||||
padding-bottom: calc(2 * var(--spacing-unit));
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
import { FormInitializer } from "talk-framework/lib/form";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import {
|
||||
Button,
|
||||
CallOut,
|
||||
Flex,
|
||||
HorizontalGutter,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import PremodConfigContainer from "../containers/PremodConfigContainer";
|
||||
import PremodLinksConfigContainer from "../containers/PremodLinksConfigContainer";
|
||||
|
||||
import styles from "./ConfigureCommentStream.css";
|
||||
|
||||
interface Props {
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
storySettings: PropTypesOf<typeof PremodConfigContainer>["storySettings"] &
|
||||
PropTypesOf<typeof PremodLinksConfigContainer>["storySettings"];
|
||||
}
|
||||
|
||||
const ConfigureCommentStream: StatelessComponent<Props> = ({
|
||||
onSubmit,
|
||||
storySettings,
|
||||
}) => (
|
||||
<Form onSubmit={onSubmit}>
|
||||
{({ handleSubmit, submitting, pristine, form, submitError }) => (
|
||||
<FormInitializer form={form}>
|
||||
{({ onInitValues }) => (
|
||||
<form autoComplete="off" onSubmit={handleSubmit} id="configure-form">
|
||||
<Flex
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-start"
|
||||
itemGutter
|
||||
>
|
||||
<Localized id="configure-commentStream-title">
|
||||
<Typography variant="heading2" className={styles.heading}>
|
||||
Configure this Comment Stream
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Localized id="configure-commentStream-apply">
|
||||
<Button
|
||||
color="success"
|
||||
variant="filled"
|
||||
type="submit"
|
||||
disabled={submitting || pristine}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</Localized>
|
||||
</Flex>
|
||||
<HorizontalGutter size="double">
|
||||
{submitError && <CallOut color="error">{submitError}</CallOut>}
|
||||
<PremodConfigContainer
|
||||
onInitValues={onInitValues}
|
||||
storySettings={storySettings}
|
||||
disabled={submitting}
|
||||
/>
|
||||
<PremodLinksConfigContainer
|
||||
onInitValues={onInitValues}
|
||||
storySettings={storySettings}
|
||||
disabled={submitting}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
</FormInitializer>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
|
||||
export default ConfigureCommentStream;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import ToggleConfig from "./ToggleConfig";
|
||||
import WidthLimitedDescription from "./WidthLimitedDescription";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const parse = (v: boolean) => {
|
||||
return v ? "PRE" : "POST";
|
||||
};
|
||||
|
||||
const format = (v: "PRE" | "POST") => {
|
||||
return v === "PRE";
|
||||
};
|
||||
|
||||
const PremodConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<Field name="moderation" type="checkbox" parse={parse} format={format}>
|
||||
{({ input }) => (
|
||||
<ToggleConfig
|
||||
id={input.name}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
onFocus={input.onFocus}
|
||||
onBlur={input.onBlur}
|
||||
checked={input.checked}
|
||||
disabled={disabled}
|
||||
title={
|
||||
<Localized id="configure-premod-title">
|
||||
<span>Pre-Moderation</span>
|
||||
</Localized>
|
||||
}
|
||||
>
|
||||
<Localized id="configure-premod-description">
|
||||
<WidthLimitedDescription>
|
||||
Moderators must approve any comment before it is published to this
|
||||
stream.
|
||||
</WidthLimitedDescription>
|
||||
</Localized>
|
||||
</ToggleConfig>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
|
||||
export default PremodConfig;
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import { parseBool } from "talk-framework/lib/form";
|
||||
|
||||
import ToggleConfig from "./ToggleConfig";
|
||||
import WidthLimitedDescription from "./WidthLimitedDescription";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const PremodLinksConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<Field name="premodLinksEnable" type="checkbox" parse={parseBool}>
|
||||
{({ input }) => (
|
||||
<ToggleConfig
|
||||
id={input.name}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
onFocus={input.onFocus}
|
||||
onBlur={input.onBlur}
|
||||
checked={input.checked}
|
||||
disabled={disabled}
|
||||
title={
|
||||
<Localized id="configure-premodLink-title">
|
||||
<span>Pre-Moderate Comments Containing Links</span>
|
||||
</Localized>
|
||||
}
|
||||
>
|
||||
<Localized id="configure-premodLink-description">
|
||||
<WidthLimitedDescription>
|
||||
Moderators must approve any comment that contains a link before it
|
||||
is published to this stream.
|
||||
</WidthLimitedDescription>
|
||||
</Localized>
|
||||
</ToggleConfig>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
|
||||
export default PremodLinksConfig;
|
||||
@@ -0,0 +1,4 @@
|
||||
.details {
|
||||
margin-top: 2.5px;
|
||||
margin-left: 25px;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Omit, PropTypesOf } from "talk-framework/types";
|
||||
import { CheckBox, Typography } from "talk-ui/components";
|
||||
|
||||
import styles from "./ToggleConfig.css";
|
||||
|
||||
export interface Props extends Omit<PropTypesOf<typeof CheckBox>, "children"> {
|
||||
title: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ToggleConfig: StatelessComponent<Props> = props => {
|
||||
const { title, children, ...rest } = props;
|
||||
return (
|
||||
<div>
|
||||
<CheckBox {...rest}>
|
||||
<Typography variant="heading3" container="span">
|
||||
{title}
|
||||
</Typography>
|
||||
</CheckBox>
|
||||
{children && <div className={styles.details}>{children}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToggleConfig;
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
max-width: 350px;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Omit, PropTypesOf } from "talk-framework/types";
|
||||
import { CheckBox, Typography } from "talk-ui/components";
|
||||
|
||||
import styles from "./WidthLimitedDescription.css";
|
||||
|
||||
export interface Props extends Omit<PropTypesOf<typeof CheckBox>, "children"> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const WidthLimitedDescription: StatelessComponent<Props> = props => {
|
||||
return (
|
||||
<Typography variant="detail" color="textSecondary" className={styles.root}>
|
||||
{props.children}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
export default WidthLimitedDescription;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { FormApi } from "final-form";
|
||||
import React from "react";
|
||||
|
||||
import { SubmitHookHandler } from "talk-framework/lib/form";
|
||||
import { graphql, withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { ConfigureCommentStreamContainer_story as StoryData } from "talk-stream/__generated__/ConfigureCommentStreamContainer_story.graphql";
|
||||
import {
|
||||
UpdateStorySettingsInput,
|
||||
UpdateStorySettingsMutation,
|
||||
withUpdateStorySettingsMutation,
|
||||
} from "talk-stream/mutations";
|
||||
|
||||
import ConfigureCommentStream from "../components/ConfigureCommentStream";
|
||||
|
||||
interface Props {
|
||||
story: StoryData;
|
||||
updateStorySettings: UpdateStorySettingsMutation;
|
||||
}
|
||||
|
||||
class ConfigureCommentStreamContainer extends React.Component<Props> {
|
||||
private handleExecute = async (
|
||||
data: UpdateStorySettingsInput["settings"],
|
||||
form: FormApi
|
||||
) => {
|
||||
await this.props.updateStorySettings({
|
||||
id: this.props.story.id,
|
||||
settings: data,
|
||||
});
|
||||
form.initialize(data);
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<SubmitHookHandler onExecute={this.handleExecute}>
|
||||
{({ onSubmit }) => (
|
||||
<ConfigureCommentStream
|
||||
onSubmit={onSubmit}
|
||||
storySettings={this.props.story.settings}
|
||||
/>
|
||||
)}
|
||||
</SubmitHookHandler>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
story: graphql`
|
||||
fragment ConfigureCommentStreamContainer_story on Story {
|
||||
id
|
||||
settings {
|
||||
...PremodConfigContainer_storySettings
|
||||
...PremodLinksConfigContainer_storySettings
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(withUpdateStorySettingsMutation(ConfigureCommentStreamContainer));
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { ConfigureContainer_me as MeData } from "talk-stream/__generated__/ConfigureContainer_me.graphql";
|
||||
import { ConfigureContainer_settings as SettingsData } from "talk-stream/__generated__/ConfigureContainer_settings.graphql";
|
||||
import { ConfigureContainer_story as StoryData } from "talk-stream/__generated__/ConfigureContainer_story.graphql";
|
||||
|
||||
import Configure from "../components/Configure";
|
||||
|
||||
interface ConfigureContainerProps {
|
||||
me: MeData;
|
||||
settings: SettingsData;
|
||||
story: StoryData;
|
||||
}
|
||||
|
||||
export class StreamContainer extends React.Component<ConfigureContainerProps> {
|
||||
public render() {
|
||||
return (
|
||||
<Configure
|
||||
story={this.props.story}
|
||||
settings={this.props.settings}
|
||||
me={this.props.me}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
const enhanced = withFragmentContainer<ConfigureContainerProps>({
|
||||
story: graphql`
|
||||
fragment ConfigureContainer_story on Story {
|
||||
...ConfigureCommentStreamContainer_story
|
||||
}
|
||||
`,
|
||||
me: graphql`
|
||||
fragment ConfigureContainer_me on User {
|
||||
...UserBoxContainer_me
|
||||
}
|
||||
`,
|
||||
settings: graphql`
|
||||
fragment ConfigureContainer_settings on Settings {
|
||||
...UserBoxContainer_settings
|
||||
}
|
||||
`,
|
||||
})(StreamContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { PremodConfigContainer_storySettings as StorySettingsData } from "talk-stream/__generated__/PremodConfigContainer_storySettings.graphql";
|
||||
|
||||
import PremodConfig from "../components/PremodConfig";
|
||||
|
||||
interface Props {
|
||||
storySettings: StorySettingsData;
|
||||
onInitValues: (values: StorySettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class PremodConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.storySettings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <PremodConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
storySettings: graphql`
|
||||
fragment PremodConfigContainer_storySettings on StorySettings {
|
||||
moderation
|
||||
}
|
||||
`,
|
||||
})(PremodConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { PremodLinksConfigContainer_storySettings as StorySettingsData } from "talk-stream/__generated__/PremodLinksConfigContainer_storySettings.graphql";
|
||||
|
||||
import PremodLinksConfig from "../components/PremodLinksConfig";
|
||||
|
||||
interface Props {
|
||||
storySettings: StorySettingsData;
|
||||
onInitValues: (values: StorySettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class PremodLinksConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.storySettings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <PremodLinksConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
storySettings: graphql`
|
||||
fragment PremodLinksConfigContainer_storySettings on StorySettings {
|
||||
premodLinksEnable
|
||||
}
|
||||
`,
|
||||
})(PremodLinksConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,106 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { once } from "lodash";
|
||||
import React, { StatelessComponent, Suspense } from "react";
|
||||
import { ReadyState } from "react-relay";
|
||||
|
||||
import {
|
||||
graphql,
|
||||
QueryRenderer,
|
||||
withLocalStateContainer,
|
||||
} from "talk-framework/lib/relay";
|
||||
import { ConfigureQuery as QueryTypes } from "talk-stream/__generated__/ConfigureQuery.graphql";
|
||||
import { ConfigureQueryLocal as Local } from "talk-stream/__generated__/ConfigureQueryLocal.graphql";
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
|
||||
const loadConfigureContainer = () =>
|
||||
import("../containers/ConfigureContainer" /* webpackChunkName: "configure" */);
|
||||
// (cvle) For some reason without `setTimeout` this request will block other requests.
|
||||
const preloadConfigureContainer = once(() =>
|
||||
setTimeout(loadConfigureContainer)
|
||||
);
|
||||
|
||||
const LazyConfigureContainer = React.lazy(loadConfigureContainer);
|
||||
|
||||
interface Props {
|
||||
local: Local;
|
||||
}
|
||||
|
||||
export const render = ({
|
||||
error,
|
||||
props,
|
||||
}: ReadyState<QueryTypes["response"]>) => {
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
|
||||
// TODO: use official React API once it has one :-)
|
||||
preloadConfigureContainer();
|
||||
|
||||
if (props) {
|
||||
if (!props.me) {
|
||||
return (
|
||||
<Localized id="configure-configureQuery-errorLoadingConfigure">
|
||||
<div>Error loading configure</div>
|
||||
</Localized>
|
||||
);
|
||||
}
|
||||
if (!props.story) {
|
||||
return (
|
||||
<Localized id="configure-configureQuery-storyNotFound">
|
||||
<div>Story not found</div>
|
||||
</Localized>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LazyConfigureContainer
|
||||
me={props.me}
|
||||
story={props.story}
|
||||
settings={props.settings}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Delay>
|
||||
<Spinner />
|
||||
</Delay>
|
||||
);
|
||||
};
|
||||
|
||||
const ConfigureQuery: StatelessComponent<Props> = ({
|
||||
local: { storyID, storyURL },
|
||||
}) => (
|
||||
<QueryRenderer<QueryTypes>
|
||||
query={graphql`
|
||||
query ConfigureQuery($storyID: ID, $storyURL: String) {
|
||||
story(id: $storyID, url: $storyURL) {
|
||||
...ConfigureContainer_story
|
||||
}
|
||||
me {
|
||||
...ConfigureContainer_me
|
||||
}
|
||||
settings {
|
||||
...ConfigureContainer_settings
|
||||
}
|
||||
}
|
||||
`}
|
||||
variables={{
|
||||
storyID,
|
||||
storyURL,
|
||||
}}
|
||||
render={render}
|
||||
/>
|
||||
);
|
||||
|
||||
const enhanced = withLocalStateContainer(
|
||||
graphql`
|
||||
fragment ConfigureQueryLocal on Local {
|
||||
storyID
|
||||
storyURL
|
||||
}
|
||||
`
|
||||
)(ConfigureQuery);
|
||||
|
||||
export default enhanced;
|
||||
@@ -6,7 +6,3 @@
|
||||
.replies {
|
||||
composes: button from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: calc(var(--spacing-unit) * 2) 0 !important;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Flex,
|
||||
HorizontalGutter,
|
||||
Icon,
|
||||
MatchMedia,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
@@ -67,9 +66,6 @@ const HistoryComment: StatelessComponent<HistoryCommentProps> = props => {
|
||||
</Localized>
|
||||
</Button>
|
||||
</Flex>
|
||||
<MatchMedia lteWidth="xs">
|
||||
<hr className={styles.divider} />
|
||||
</MatchMedia>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
-7
@@ -66,12 +66,5 @@ exports[`renders correctly 1`] = `
|
||||
</Localized>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<MatchMediaWithContext
|
||||
lteWidth="xs"
|
||||
>
|
||||
<hr
|
||||
className="HistoryComment-divider"
|
||||
/>
|
||||
</MatchMediaWithContext>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
@@ -14,7 +14,7 @@ interface ProfileContainerProps {
|
||||
story: StoryData;
|
||||
}
|
||||
|
||||
export class StreamContainer extends React.Component<ProfileContainerProps> {
|
||||
export class ProfileContainer extends React.Component<ProfileContainerProps> {
|
||||
public render() {
|
||||
return (
|
||||
<Profile
|
||||
@@ -42,6 +42,6 @@ const enhanced = withFragmentContainer<ProfileContainerProps>({
|
||||
...UserBoxContainer_settings
|
||||
}
|
||||
`,
|
||||
})(StreamContainer);
|
||||
})(ProfileContainer);
|
||||
|
||||
export default enhanced;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { once } from "lodash";
|
||||
import React, { StatelessComponent, Suspense } from "react";
|
||||
import { ReadyState } from "react-relay";
|
||||
|
||||
import {
|
||||
@@ -11,7 +12,12 @@ import { ProfileQuery as QueryTypes } from "talk-stream/__generated__/ProfileQue
|
||||
import { ProfileQueryLocal as Local } from "talk-stream/__generated__/ProfileQueryLocal.graphql";
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
|
||||
import ProfileContainer from "../containers/ProfileContainer";
|
||||
const loadProfileContainer = () =>
|
||||
import("../containers/ProfileContainer" /* webpackChunkName: "profile" */);
|
||||
// (cvle) For some reason without `setTimeout` this request will block other requests.
|
||||
const preloadProfileContainer = once(() => setTimeout(loadProfileContainer));
|
||||
|
||||
const LazyProfileContainer = React.lazy(loadProfileContainer);
|
||||
|
||||
interface Props {
|
||||
local: Local;
|
||||
@@ -25,6 +31,9 @@ export const render = ({
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
|
||||
// TODO: use official React API once it has one :-)
|
||||
preloadProfileContainer();
|
||||
|
||||
if (props) {
|
||||
if (!props.me) {
|
||||
return (
|
||||
@@ -35,17 +44,19 @@ export const render = ({
|
||||
}
|
||||
if (!props.story) {
|
||||
return (
|
||||
<Localized id="comments-profileQuery-storyNotFound">
|
||||
<Localized id="profile-profileQuery-storyNotFound">
|
||||
<div>Story not found</div>
|
||||
</Localized>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ProfileContainer
|
||||
me={props.me}
|
||||
story={props.story}
|
||||
settings={props.settings}
|
||||
/>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LazyProfileContainer
|
||||
me={props.me}
|
||||
story={props.story}
|
||||
settings={props.settings}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ async function createTestRenderer(
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
story: sinon.stub().callsFake((_: any, variables: any) => {
|
||||
expect(variables.id).toBe(stories[0].id);
|
||||
expectAndFail(variables.id).toBe(stories[0].id);
|
||||
return stories[0];
|
||||
}),
|
||||
...resolver.Query,
|
||||
|
||||
@@ -35,7 +35,7 @@ function createTestRenderer(
|
||||
},
|
||||
Mutation: {
|
||||
editComment: sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
commentID: commentWithReplies.id,
|
||||
body: "Edited!",
|
||||
|
||||
@@ -25,7 +25,7 @@ async function createTestRenderer(
|
||||
settings: sinon.stub().returns(settings),
|
||||
me: sinon.stub().returns(users[0]),
|
||||
story: sinon.stub().callsFake((_: any, variables: any) => {
|
||||
expect(variables.id).toBe(stories[0].id);
|
||||
expectAndFail(variables.id).toBe(stories[0].id);
|
||||
return stories[0];
|
||||
}),
|
||||
...resolver.Query,
|
||||
@@ -72,7 +72,7 @@ it("post a comment", async () => {
|
||||
const { rte, form, tabPane } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
@@ -119,7 +119,7 @@ const postACommentAndHandleNonVisibleComment = async (
|
||||
const { rte, form } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
body: "<b>Hello world!</b>",
|
||||
|
||||
@@ -32,7 +32,7 @@ beforeEach(() => {
|
||||
},
|
||||
Mutation: {
|
||||
createCommentReply: sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toMatchObject({
|
||||
expectAndFail(data.input).toMatchObject({
|
||||
storyID: storyWithDeepestReplies.id,
|
||||
parentID: "comment-with-deepest-replies-5",
|
||||
parentRevisionID: "revision-0",
|
||||
|
||||
@@ -23,7 +23,7 @@ async function createTestRenderer(
|
||||
settings: sinon.stub().returns(settings),
|
||||
me: sinon.stub().returns(users[0]),
|
||||
story: sinon.stub().callsFake((_: any, variables: any) => {
|
||||
expect(variables.id).toBe(stories[0].id);
|
||||
expectAndFail(variables.id).toBe(stories[0].id);
|
||||
return stories[0];
|
||||
}),
|
||||
...resolver.Query,
|
||||
@@ -75,7 +75,7 @@ it("post a reply", async () => {
|
||||
const { testRenderer, comment, rte, form } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createCommentReply: sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
parentID: stories[0].comments.edges[0].node.id,
|
||||
@@ -127,7 +127,7 @@ it("post a reply and handle non-visible comment state", async () => {
|
||||
const { comment, rte, form } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createCommentReply: sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toMatchObject({
|
||||
expectAndFail(data).toMatchObject({
|
||||
input: {
|
||||
storyID: stories[0].id,
|
||||
parentID: stories[0].comments.edges[0].node.id,
|
||||
|
||||
@@ -9,7 +9,7 @@ function createTestRenderer() {
|
||||
const resolvers = {
|
||||
Query: {
|
||||
story: sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data).toEqual({
|
||||
expectAndFail(data).toEqual({
|
||||
id: stories[0].id,
|
||||
url: null,
|
||||
});
|
||||
@@ -20,7 +20,7 @@ function createTestRenderer() {
|
||||
},
|
||||
Mutation: {
|
||||
createCommentReaction: sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toMatchObject({
|
||||
expectAndFail(data.input).toMatchObject({
|
||||
commentID: stories[0].comments.edges[0].node.id,
|
||||
commentRevisionID: stories[0].comments.edges[0].node.revision.id,
|
||||
});
|
||||
@@ -35,7 +35,7 @@ function createTestRenderer() {
|
||||
};
|
||||
}),
|
||||
removeCommentReaction: sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toMatchObject({
|
||||
expectAndFail(data.input).toMatchObject({
|
||||
commentID: stories[0].comments.edges[0].node.id,
|
||||
});
|
||||
return {
|
||||
|
||||
@@ -14,7 +14,7 @@ async function createTestRenderer(
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
story: sinon.stub().callsFake((_: any, variables: any) => {
|
||||
expect(variables.id).toBe(stories[0].id);
|
||||
expectAndFail(variables.id).toBe(stories[0].id);
|
||||
return stories[0];
|
||||
}),
|
||||
...resolver.Query,
|
||||
|
||||
@@ -15,7 +15,7 @@ function createTestRenderer(
|
||||
const resolvers = {
|
||||
Query: {
|
||||
story: sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data).toEqual({
|
||||
expectAndFail(data).toEqual({
|
||||
id: stories[0].id,
|
||||
url: null,
|
||||
});
|
||||
@@ -27,7 +27,7 @@ function createTestRenderer(
|
||||
},
|
||||
Mutation: {
|
||||
createCommentFlag: sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toMatchObject({
|
||||
expectAndFail(data.input).toMatchObject({
|
||||
commentID: stories[0].comments.edges[0].node.id,
|
||||
commentRevisionID: stories[0].comments.edges[0].node.revision.id,
|
||||
reason: "COMMENT_REPORTED_OFFENSIVE",
|
||||
@@ -42,7 +42,7 @@ function createTestRenderer(
|
||||
};
|
||||
}),
|
||||
createCommentDontAgree: sinon.stub().callsFake((_: any, data: any) => {
|
||||
expect(data.input).toMatchObject({
|
||||
expectAndFail(data.input).toMatchObject({
|
||||
commentID: stories[0].comments.edges[0].node.id,
|
||||
commentRevisionID: stories[0].comments.edges[0].node.revision.id,
|
||||
additionalDetails: "More info",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user