diff --git a/.circleci/config.yml b/.circleci/config.yml index dd98d8c41..721a4c701 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,6 +45,9 @@ jobs: - checkout - attach_workspace: at: ~/coralproject/talk + - run: + name: Compile schemas and types + command: npm run compile - run: name: Perform linting command: npm run lint diff --git a/package-lock.json b/package-lock.json index a62e7d284..cca10df6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1342,9 +1342,9 @@ } }, "@coralproject/rte": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-0.10.6.tgz", - "integrity": "sha512-oUK/KSw28AkmpX5D8v1IkQT0iIxKxPmSG0euTFgqEthSgnAUQgoOmQengaR/nsS9zE31KPbbWMIAzH0aN/YwNA==", + "version": "0.10.9", + "resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-0.10.9.tgz", + "integrity": "sha512-0K+bc3JaOhjgJJ91uupev4EmSoldd/IizfjRgAQ8LbZjRc3pWprzIBhoTMChApBzz7eI9DUfw2ULg5muziV8BA==", "dev": true, "requires": { "bowser": "^1.0.0", @@ -1580,6 +1580,15 @@ "lodash.deburr": "^4.1.0" } }, + "@sinonjs/commons": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.0.2.tgz", + "integrity": "sha512-WR3dlgqJP4QNrLC4iXN/5/2WaLQQ0VijOOkmflqFGVJ6wLEpbSjo7c0ZeGIdtY8Crk7xBBp87sM6+Mkerz7alw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, "@sinonjs/formatio": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", @@ -2069,9 +2078,14 @@ } }, "@types/react-relay": { - "version": "github:coralproject/patched#ba4c8d01bb737e5f073534b32d870294e39cc5a8", - "from": "github:coralproject/patched#types/react-relay", - "dev": true + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@types/react-relay/-/react-relay-1.3.9.tgz", + "integrity": "sha512-DFhFrEiDUYxR6VWpkf14CnzS7vtpWaVku0EmnxT2X4U45rOUP75nnOEnRCixZyyxIgr7ULpg5Zc1G9VJpQF7GQ==", + "dev": true, + "requires": { + "@types/react": "*", + "@types/relay-runtime": "*" + } }, "@types/react-responsive": { "version": "3.0.1", @@ -2107,8 +2121,9 @@ "dev": true }, "@types/relay-runtime": { - "version": "github:coralproject/patched#ba8d413696e97b4f67450de3525cc319b9980cba", - "from": "github:coralproject/patched#types/relay-runtime", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@types/relay-runtime/-/relay-runtime-1.3.6.tgz", + "integrity": "sha512-NobpY0XFh0O9FTRkFJsN5ZNd+M+4eac8ZgM5LP3C0tLd1JkfrwuGkyBUMTuDDGYf+T5zl6vTO6EGgXKD+0QpGA==", "dev": true }, "@types/sane": { @@ -2188,6 +2203,15 @@ "@types/node": "*" } }, + "@types/vinyl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.2.tgz", + "integrity": "sha512-2iYpNuOl98SrLPBZfEN9Mh2JCJ2EI9HU35SfgBEb51DcmaHkhp8cKMblYeBqMQiwXMgAD3W60DbQ4i/UdLiXhw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/webpack": { "version": "4.4.7", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.4.7.tgz", @@ -3697,13 +3721,14 @@ } }, "babel-plugin-relay": { - "version": "github:coralproject/patched#80c179a21ece1a43e4782c776f3041fe5ab14a4a", - "from": "github:coralproject/patched#babel-plugin-relay", + "version": "1.7.0-rc.1", + "resolved": "https://registry.npmjs.org/babel-plugin-relay/-/babel-plugin-relay-1.7.0-rc.1.tgz", + "integrity": "sha512-VklvYrB0kSZeDHGtwJNtDtoQNVrI+ZclURnsCBwWU5oIX4f21//cYeQ9oooJDIvXcCApFKhaozP5tfWgk+Fo5g==", "dev": true, "requires": { + "babel-plugin-macros": "^2.0.0", "babel-runtime": "^6.23.0", - "babel-types": "^6.24.1", - "graphql": "^0.13.0" + "babel-types": "^6.24.1" } }, "babel-plugin-syntax-class-properties": { @@ -4043,9 +4068,9 @@ } }, "babel-preset-fbjs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.1.4.tgz", - "integrity": "sha512-6XVQwlO26V5/0P9s2Eje8Epqkv/ihaMJ798+W98ktOA8fCn2IFM6wEi7CDW3fTbKFZ/8fDGvGZH01B6GSuNiWA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.2.0.tgz", + "integrity": "sha512-jj0KFJDioYZMtPtZf77dQuU+Ad/1BtN0UnAYlHDa8J8f4tGXr3YrPoJImD5MdueaOPeN/jUdrCgu330EfXr0XQ==", "dev": true, "requires": { "babel-plugin-check-es2015-constants": "^6.8.0", @@ -5157,12 +5182,6 @@ } } }, - "babylon": { - "version": "7.0.0-beta.31", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.31.tgz", - "integrity": "sha512-6lm2mV3S51yEnKmQQNnswoABL1U1H1KHoCCVwdwI3hvIv+W7ya4ki7Aw4o4KxtUHjNKkK5WpZb22rrMMOcJXJQ==", - "dev": true - }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -8430,6 +8449,12 @@ "long": "^3.2.0" } }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", @@ -8490,14 +8515,6 @@ "dev": true, "requires": { "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } } } } @@ -8548,6 +8565,21 @@ "path-exists": "^3.0.0" } }, + "minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "p-limit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", @@ -8573,9 +8605,9 @@ "dev": true }, "react-dev-utils": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-5.0.1.tgz", - "integrity": "sha512-+y92rG6pmXt3cpcg/NGmG4w/W309tWNSmyyPL8hCMxuCSg2UP/hUg3npACj2UZc8UKVSXexyLrCnxowizGoAsw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-5.0.2.tgz", + "integrity": "sha512-d2FbKvYe4XAQx5gjHBoWG+ADqC3fGZzjb7i9vxd/Y5xfLkBGtQyX7aOb8lBRQPYUhjngiD3d49LevjY1stUR0Q==", "dev": true, "requires": { "address": "1.0.3", @@ -8590,10 +8622,10 @@ "inquirer": "3.3.0", "is-root": "1.0.0", "opn": "5.2.0", - "react-error-overlay": "^4.0.0", + "react-error-overlay": "^4.0.1", "recursive-readdir": "2.2.1", "shell-quote": "1.6.1", - "sockjs-client": "1.1.4", + "sockjs-client": "1.1.5", "strip-ansi": "3.0.1", "text-table": "0.2.0" }, @@ -8610,6 +8642,121 @@ "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "filesize": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.11.tgz", + "integrity": "sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g==", + "dev": true + }, + "gzip-size": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", + "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=", + "dev": true, + "requires": { + "duplexer": "^0.1.1" + } + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "react-error-overlay": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-4.0.1.tgz", + "integrity": "sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw==", + "dev": true + }, + "recursive-readdir": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.1.tgz", + "integrity": "sha1-kO8jHQd4xc4JPJpI105cVCLROpk=", + "dev": true, + "requires": { + "minimatch": "3.0.3" + } + }, + "sockjs-client": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.5.tgz", + "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=", + "dev": true, + "requires": { + "debug": "^2.6.6", + "eventsource": "0.1.6", + "faye-websocket": "~0.11.0", + "inherits": "^2.0.1", + "json3": "^3.3.2", + "url-parse": "^1.1.8" + } } } }, @@ -9792,12 +9939,6 @@ } } }, - "filesize": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.11.tgz", - "integrity": "sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g==", - "dev": true - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -11095,6 +11236,32 @@ "iterall": "^1.2.1" } }, + "graphql-compiler": { + "version": "1.7.0-rc.1", + "resolved": "https://registry.npmjs.org/graphql-compiler/-/graphql-compiler-1.7.0-rc.1.tgz", + "integrity": "sha512-wu1HMcI39I2Ion1N+nrn9ELGMFGi6lFDz+dJuJwnlO3BdXTrSVvYnnusxNaim4/Gkmf9tM/ErYSbpvRzBWPk3g==", + "dev": true, + "requires": { + "chalk": "^1.1.1", + "fb-watchman": "^2.0.0", + "immutable": "~3.7.6" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + } + } + }, "graphql-config": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-2.0.1.tgz", @@ -11518,15 +11685,6 @@ "glogg": "^1.0.0" } }, - "gzip-size": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", - "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=", - "dev": true, - "requires": { - "duplexer": "^0.1.1" - } - }, "handle-thing": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", @@ -12208,45 +12366,6 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, "internal-ip": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", @@ -15120,9 +15239,9 @@ "dev": true }, "just-extend": { - "version": "1.1.27", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", - "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-3.0.0.tgz", + "integrity": "sha512-Fu3T6pKBuxjWT/p4DkqGHFRsysc8OauWr4ZRTY9dIx07Y9O0RkoR5jcv28aeD1vuAwhm3nLkDurwLXoALp4DpQ==", "dev": true }, "jwa": { @@ -16670,13 +16789,13 @@ "dev": true }, "nise": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.2.tgz", - "integrity": "sha512-BxH/DxoQYYdhKgVAfqVy4pzXRZELHOIewzoesxpjYvpU+7YOalQhGNPf7wAx8pLrTNPrHRDlLOkAl8UI0ZpXjw==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.4.tgz", + "integrity": "sha512-pxE0c9PzgrUTyhfv5p+5eMIdfU2bLEsq8VQEuE0kxM4zP7SujSar7rk9wpI2F7RyyCEvLyj5O7Is3RER5F36Fg==", "dev": true, "requires": { "@sinonjs/formatio": "^2.0.0", - "just-extend": "^1.1.27", + "just-extend": "^3.0.0", "lolex": "^2.3.2", "path-to-regexp": "^1.7.0", "text-encoding": "^0.6.4" @@ -20780,12 +20899,6 @@ "create-emotion-styled": "^9.2.6" } }, - "react-error-overlay": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-4.0.0.tgz", - "integrity": "sha512-FlsPxavEyMuR6TjVbSSywovXSEyOg6ZDj5+Z8nbsRl9EkOzAhEIcS+GLoQDC5fz/t9suhUXWmUrOBrgeUvrMxw==", - "dev": true - }, "react-feather": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/react-feather/-/react-feather-1.1.1.tgz", @@ -20889,13 +21002,15 @@ } }, "react-relay": { - "version": "github:coralproject/patched#aad037f8cbcd9c14d5f6fd96cfafb24c39eab937", - "from": "github:coralproject/patched#react-relay", + "version": "1.7.0-rc.1", + "resolved": "https://registry.npmjs.org/react-relay/-/react-relay-1.7.0-rc.1.tgz", + "integrity": "sha512-5BcnFr++zkfq5AVoCjcqr8eotuJwVN+57+BnhJZuw3EoY8Yi+avV/AqZ7Tz9F6r9p5xZkCoYZw3kRTtpK9lPSQ==", "dev": true, "requires": { "babel-runtime": "^6.23.0", - "fbjs": "^0.8.14", - "prop-types": "^15.5.8" + "fbjs": "0.8.17", + "prop-types": "^15.5.8", + "relay-runtime": "1.7.0-rc.1" } }, "react-responsive": { @@ -21120,26 +21235,6 @@ "symbol-observable": "^1.0.4" } }, - "recursive-readdir": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.1.tgz", - "integrity": "sha1-kO8jHQd4xc4JPJpI105cVCLROpk=", - "dev": true, - "requires": { - "minimatch": "3.0.3" - }, - "dependencies": { - "minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", - "dev": true, - "requires": { - "brace-expansion": "^1.0.0" - } - } - } - }, "redent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", @@ -21332,27 +21427,59 @@ "dev": true }, "relay-compiler": { - "version": "github:coralproject/patched#652bdfa85cf35ed3e8dc4e26cf26e9d1744873cb", - "from": "github:coralproject/patched#relay-compiler", + "version": "1.7.0-rc.1", + "resolved": "https://registry.npmjs.org/relay-compiler/-/relay-compiler-1.7.0-rc.1.tgz", + "integrity": "sha512-NCbrAWG692dp0on3sefX0pZ+1/eWDPOnme/i0vAbH38ejfRW5vPS9pznHYS7A0y7OC5lABMZT+DCyEYeHFpflA==", "dev": true, "requires": { - "babel-generator": "^6.26.0", + "@babel/generator": "7.0.0-beta.56", + "@babel/parser": "7.0.0-beta.56", + "@babel/types": "7.0.0-beta.56", "babel-polyfill": "^6.20.0", - "babel-preset-fbjs": "^2.1.4", + "babel-preset-fbjs": "2.2.0", "babel-runtime": "^6.23.0", "babel-traverse": "^6.26.0", - "babel-types": "^6.24.1", - "babylon": "^7.0.0-beta", "chalk": "^1.1.1", - "fast-glob": "^2.0.0", + "fast-glob": "^2.2.2", "fb-watchman": "^2.0.0", - "fbjs": "^0.8.14", - "graphql": "^0.13.0", + "fbjs": "0.8.17", + "graphql-compiler": "1.7.0-rc.1", "immutable": "~3.7.6", + "relay-runtime": "1.7.0-rc.1", "signedsource": "^1.0.0", "yargs": "^9.0.0" }, "dependencies": { + "@babel/generator": { + "version": "7.0.0-beta.56", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.56.tgz", + "integrity": "sha512-d+Ls/Vr5OU5FBDYQToXSqAluI3r2UaSoNZ41zD3sxdoVoaT8K5Bdh4So4eG4o//INGM7actValXGfb+5J1+r8w==", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.56", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/parser": { + "version": "7.0.0-beta.56", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.56.tgz", + "integrity": "sha512-JM0ughhbo+sPXw2Z+SUyowfYrAOhjanzjMshcLswBdXVelJCOeEKe/FqMqPWGVPQr7wByongXIn+MKdCpY7DBw==", + "dev": true + }, + "@babel/types": { + "version": "7.0.0-beta.56", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.56.tgz", + "integrity": "sha512-fRIBeHtKxAD3D1E7hYSpG4MnLt0AfzHHs5gfVclOB0NlfLu3qiWU/IqdbK2ixTK61424iEkV1P/VAzndx6ungA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + } + }, "babel-polyfill": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", @@ -21535,8 +21662,9 @@ } }, "relay-compiler-language-typescript": { - "version": "github:coralproject/patched#0da4835cf006e905c154d1e41aa978f81ace54f3", - "from": "github:coralproject/patched#relay-compiler-language-typescript", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/relay-compiler-language-typescript/-/relay-compiler-language-typescript-1.1.0.tgz", + "integrity": "sha512-HDWTEsSsNuphmNrjbc1Tf1ljVjbi2CSA7zArsFFYCYHfVU/O7oS3qv/u5E+LrHdYSM80AjpUVjdcFGsqkgOmNw==", "dev": true, "requires": { "immutable": "^3.7.6", @@ -21550,23 +21678,13 @@ "dev": true }, "relay-runtime": { - "version": "github:coralproject/patched#9dda7718393cb3aa6647bd3badf574c025f79d12", - "from": "github:coralproject/patched#relay-runtime", + "version": "1.7.0-rc.1", + "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-1.7.0-rc.1.tgz", + "integrity": "sha512-gdhtIKojPA0FJPc4hPGAbxZBAR2xpnCvCuoL4/wu3ffHxwUFvSSlBIptW3GSxTRM2F55vl7I1IeehCS9IAfQPA==", "dev": true, "requires": { "babel-runtime": "^6.23.0", - "fbjs": "^0.8.14" - } - }, - "relay-test-utils": { - "version": "github:coralproject/patched#eee4232d3996db88c4ae9030023adcf1f3674615", - "from": "github:coralproject/patched#relay-test-utils", - "dev": true, - "requires": { - "babel-runtime": "^6.23.0", - "fbjs": "^0.8.14", - "graphql": "^0.13.0", - "prop-types": "^15.5.8" + "fbjs": "0.8.17" } }, "remark": { @@ -22406,25 +22524,26 @@ "dev": true }, "sinon": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.1.3.tgz", - "integrity": "sha512-yeTza8xIZZdiXntCHJAzKll/sSYE+DuJOS8hiSapzaLqdW8eCNVVC9je9SZYYTkPm2bLts9x6UYxwuMAVVrM6Q==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.1.5.tgz", + "integrity": "sha512-TcbRoWs1SdY6NOqfj0c9OEQquBoZH+qEf8799m1jjcbfWrrpyCQ3B/BpX7+NKa7Vn33Jl+Z50H4Oys3bzygK2Q==", "dev": true, "requires": { + "@sinonjs/commons": "^1.0.1", "@sinonjs/formatio": "^2.0.0", "@sinonjs/samsam": "^2.0.0", "diff": "^3.5.0", "lodash.get": "^4.4.2", - "lolex": "^2.4.2", - "nise": "^1.3.3", + "lolex": "^2.7.1", + "nise": "^1.4.2", "supports-color": "^5.4.0", "type-detect": "^4.0.8" }, "dependencies": { "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -23396,6 +23515,12 @@ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", "dev": true }, + "timekeeper": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-2.1.2.tgz", + "integrity": "sha512-fc1DDqbiyz5vxRO4xkiATwfWUw1FV7W20+FJYal1SnoIYgNuB4WNxYLtbG3zjUBwOSk3P4u1TgBAZYG/aqBWMw==", + "dev": true + }, "timers-browserify": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", @@ -24153,9 +24278,9 @@ "dev": true }, "typescript": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz", - "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.3.tgz", + "integrity": "sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg==", "dev": true }, "ua-parser-js": { diff --git a/package.json b/package.json index fe98d87a8..13bc586c8 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "start": "node dist/index.js", "start:development": "ts-node --project ./src/tsconfig.json -r tsconfig-paths/register ./src/index.ts", "start:webpackDevServer": "ts-node ./scripts/start.ts", - "lint": "npm-run-all --parallel lint:*", + "lint": "npm-run-all --parallel lint:* tscheck:*", "lint:server": "tslint --project ./src/tsconfig.json", "lint:client": "tslint --project ./src/core/client/tsconfig.json", "lint:client-embed": "tslint --project ./src/core/client/embed/tsconfig.json", @@ -76,7 +76,7 @@ "@babel/polyfill": "7.0.0-beta.49", "@babel/preset-env": "7.0.0-beta.49", "@babel/preset-react": "7.0.0-beta.49", - "@coralproject/rte": "^0.10.6", + "@coralproject/rte": "^0.10.9", "@types/bcryptjs": "^2.4.1", "@types/bunyan": "^1.8.4", "@types/case-sensitive-paths-webpack-plugin": "^2.1.2", @@ -116,16 +116,17 @@ "@types/query-string": "^6.1.0", "@types/react-copy-to-clipboard": "^4.2.5", "@types/react-dom": "^16.0.6", - "@types/react-relay": "github:coralproject/patched#types/react-relay", + "@types/react-relay": "^1.3.9", "@types/react-responsive": "^3.0.1", "@types/react-test-renderer": "^16.0.1", "@types/recompose": "^0.26.1", - "@types/relay-runtime": "github:coralproject/patched#types/relay-runtime", + "@types/relay-runtime": "^1.3.6", "@types/sane": "^2.0.0", "@types/sinon": "^5.0.1", "@types/tlds": "^1.199.0", "@types/uglifyjs-webpack-plugin": "^1.1.0", "@types/uuid": "^3.4.3", + "@types/vinyl": "^2.0.2", "@types/webpack": "^4.4.7", "@types/webpack-dev-server": "^2.9.5", "@types/webpack-manifest-plugin": "^1.3.2", @@ -134,7 +135,7 @@ "babel-core": "^7.0.0-bridge.0", "babel-loader": "^8.0.0-beta", "babel-plugin-module-resolver": "^3.1.1", - "babel-plugin-relay": "github:coralproject/patched#babel-plugin-relay", + "babel-plugin-relay": "^1.7.0-rc.1", "babel-preset-react-optimize": "^1.0.1", "case-sensitive-paths-webpack-plugin": "^2.1.2", "chalk": "^2.4.1", @@ -194,21 +195,21 @@ "react-dom": "^16.4.0", "react-final-form": "^3.6.4", "react-popper": "^1.0.0", - "react-relay": "github:coralproject/patched#react-relay", + "react-relay": "^1.7.0-rc.1", "react-responsive": "^5.0.0", "react-test-renderer": "^16.4.2", "react-timeago": "^4.1.9", "react-with-state-props": "^2.0.4", "recompose": "^0.27.1", - "relay-compiler": "github:coralproject/patched#relay-compiler", - "relay-compiler-language-typescript": "github:coralproject/patched#relay-compiler-language-typescript", + "relay-compiler": "^1.7.0-rc.1", + "relay-compiler-language-typescript": "^1.1.0", "relay-local-schema": "^0.7.0", - "relay-runtime": "github:coralproject/patched#relay-runtime", - "relay-test-utils": "github:coralproject/patched#relay-test-utils", + "relay-runtime": "^1.7.0-rc.1", "sane": "^2.5.2", "simulant": "^0.2.2", - "sinon": "^6.1.3", + "sinon": "^6.1.5", "style-loader": "^0.21.0", + "timekeeper": "^2.1.2", "ts-jest": "^23.0.0", "ts-loader": "^4.4.2", "ts-node": "^6.2.0", @@ -222,7 +223,7 @@ "typed-css-modules": "^0.3.4", "typeface-manuale": "0.0.54", "typeface-source-sans-pro": "0.0.54", - "typescript": "^3.0.0", + "typescript": "^3.0.3", "uglifyjs-webpack-plugin": "^1.2.5", "webpack": "4.12.0", "webpack-cli": "^3.0.2", diff --git a/src/core/client/auth/components/App.tsx b/src/core/client/auth/components/App.tsx index 103bf95dc..1cdbe1f76 100644 --- a/src/core/client/auth/components/App.tsx +++ b/src/core/client/auth/components/App.tsx @@ -6,8 +6,6 @@ import ResetPasswordContainer from "../containers/ResetPasswordContainer"; import SignInContainer from "../containers/SignInContainer"; import SignUpContainer from "../containers/SignUpContainer"; -// TODO: (cvle) Remove %future added value when we have Relay 1.6 -// https://github.com/facebook/relay/commit/1e87e43add7667a494f7ff4cfa7f03f1ab8d81a2 export type View = | "SIGN_UP" | "SIGN_IN" diff --git a/src/core/client/auth/containers/AppContainer.tsx b/src/core/client/auth/containers/AppContainer.tsx index b100bfc74..7d424672e 100644 --- a/src/core/client/auth/containers/AppContainer.tsx +++ b/src/core/client/auth/containers/AppContainer.tsx @@ -14,7 +14,7 @@ const AppContainer: StatelessComponent = ({ local: { view } }) => { return ; }; -const enhanced = withLocalStateContainer( +const enhanced = withLocalStateContainer( graphql` fragment AppContainerLocal on Local { view diff --git a/src/core/client/framework/helpers/getMe.ts b/src/core/client/framework/helpers/getMe.ts new file mode 100644 index 000000000..07eeb5952 --- /dev/null +++ b/src/core/client/framework/helpers/getMe.ts @@ -0,0 +1,12 @@ +import { Environment, ROOT_ID } from "relay-runtime"; + +export default function getMe(environment: Environment) { + const source = environment.getStore().getSource(); + const root = source.get(ROOT_ID)!; + const meKey = Object.keys(root).find(s => s.startsWith("me("))!; + if (!root[meKey]) { + return null; + } + const meID = root[meKey].__ref; + return source.get(meID)!; +} diff --git a/src/core/client/framework/helpers/index.ts b/src/core/client/framework/helpers/index.ts new file mode 100644 index 000000000..653f66d98 --- /dev/null +++ b/src/core/client/framework/helpers/index.ts @@ -0,0 +1 @@ +export { default as getMe } from "./getMe"; diff --git a/src/core/client/framework/lib/relay/QueryRenderer.tsx b/src/core/client/framework/lib/relay/QueryRenderer.tsx index a5845c0b6..cc8acac40 100644 --- a/src/core/client/framework/lib/relay/QueryRenderer.tsx +++ b/src/core/client/framework/lib/relay/QueryRenderer.tsx @@ -3,23 +3,25 @@ import { QueryRenderer, QueryRendererProps as QueryRendererPropsOrig, } from "react-relay"; +import { OperationBase, OperationDefaults } from "relay-runtime"; import { Omit } from "talk-framework/types"; import { TalkContextConsumer } from "../bootstrap/TalkContext"; // Omit environment as we are passing this from the context. -export type QueryRendererProps = Omit< - QueryRendererPropsOrig, - "environment" ->; +export type QueryRendererProps< + T extends OperationBase = OperationDefaults +> = Omit, "environment">; /** * TalkQueryRenderer is a wrappper around Relay's `QueryRenderer`. * It supplies the `environment` from the context and has better * generics type support. */ -class TalkQueryRenderer extends Component> { +class TalkQueryRenderer< + T extends OperationBase = OperationDefaults +> extends Component> { public render() { return ( diff --git a/src/core/client/framework/lib/relay/commitMutationPromise.ts b/src/core/client/framework/lib/relay/commitMutationPromise.ts index 97fb23088..b94fd7dde 100644 --- a/src/core/client/framework/lib/relay/commitMutationPromise.ts +++ b/src/core/client/framework/lib/relay/commitMutationPromise.ts @@ -1,5 +1,5 @@ import { commitMutation } from "react-relay"; -import { Environment, MutationConfig } from "relay-runtime"; +import { Environment, MutationConfig, OperationBase } from "relay-runtime"; import { Omit } from "talk-framework/types"; @@ -7,8 +7,8 @@ import { Omit } from "talk-framework/types"; * Like `MutationConfig` but omits `onCompleted` and `onError` * because we are going to use a Promise API. */ -export type MutationPromiseConfig = Omit< - MutationConfig, +export type MutationPromiseConfig = Omit< + MutationConfig, "onCompleted" | "onError" >; @@ -27,10 +27,10 @@ function getPayload(response: { [key: string]: any }): any { * and errors are wrapped inside of application specific * error instances. */ -export async function commitMutationPromiseNormalized( +export async function commitMutationPromiseNormalized( environment: Environment, - config: MutationPromiseConfig -): Promise { + config: MutationPromiseConfig +): Promise { try { const response = await commitMutationPromise(environment, config); return getPayload(response); @@ -42,10 +42,10 @@ export async function commitMutationPromiseNormalized( /** * Like `commitMutation` of the Relay API but returns a Promise. */ -export function commitMutationPromise( +export function commitMutationPromise( environment: Environment, - config: MutationPromiseConfig -): Promise { + config: MutationPromiseConfig +): Promise { return new Promise((resolve, reject) => { commitMutation(environment, { ...config, diff --git a/src/core/client/framework/lib/relay/hideForwardRef.ts b/src/core/client/framework/lib/relay/hideForwardRef.ts new file mode 100644 index 000000000..434faf078 --- /dev/null +++ b/src/core/client/framework/lib/relay/hideForwardRef.ts @@ -0,0 +1,10 @@ +import React, { ComponentType, StatelessComponent } from "react"; + +// TODO: (cvle) This currently a workaround to hide ForwardRef from +// enzyme which does not support it well yet. +export default function hideForwardRef(component: ComponentType) { + const wrapped: StatelessComponent = (props: any) => + React.createElement(component, props); + wrapped.displayName = component.displayName; + return wrapped; +} diff --git a/src/core/client/framework/lib/relay/types.ts b/src/core/client/framework/lib/relay/types.ts new file mode 100644 index 000000000..07125e251 --- /dev/null +++ b/src/core/client/framework/lib/relay/types.ts @@ -0,0 +1,7 @@ +import { _RefType } from "react-relay"; + +export type FragmentKeys = { + [P in keyof T]: T[P] extends _RefType | null ? P : never +}[keyof T]; + +export type FragmentKeysNoLocal = Exclude, "local">; diff --git a/src/core/client/framework/lib/relay/withFragmentContainer.ts b/src/core/client/framework/lib/relay/withFragmentContainer.ts index 2541b2001..bbac61726 100644 --- a/src/core/client/framework/lib/relay/withFragmentContainer.ts +++ b/src/core/client/framework/lib/relay/withFragmentContainer.ts @@ -1,12 +1,30 @@ -import { createFragmentContainer, GraphQLTaggedNode } from "react-relay"; +import { + _RefType, + createFragmentContainer, + FragmentOrRegularProp, + GraphQLTaggedNode, +} from "react-relay"; import { InferableComponentEnhancerWithProps } from "recompose"; +import { wrapDisplayName } from "recompose"; + +import hideForwardRef from "./hideForwardRef"; +import { FragmentKeysNoLocal } from "./types"; /** * withFragmentContainer is a curried version of `createFragmentContainers` * from Relay. */ export default ( - fragmentSpec: { [P in keyof T]: GraphQLTaggedNode } -): InferableComponentEnhancerWithProps => ( - component: React.ComponentType -) => createFragmentContainer(component, fragmentSpec) as any; + fragmentSpec: { [P in FragmentKeysNoLocal]: GraphQLTaggedNode } & { + _?: never; + } +): InferableComponentEnhancerWithProps< + { [P in FragmentKeysNoLocal]: T[P] }, + { [P in FragmentKeysNoLocal]: FragmentOrRegularProp } +> => (component: React.ComponentType) => { + const result = createFragmentContainer(component, fragmentSpec); + result.displayName = wrapDisplayName(component, "Relay"); + // TODO: (cvle) We wrap this currently to hide the ForwardRef which is not + // well supported yet in enzyme. + return hideForwardRef(result) as any; +}; diff --git a/src/core/client/framework/lib/relay/withLocalStateContainer.tsx b/src/core/client/framework/lib/relay/withLocalStateContainer.tsx index 421eb7d71..444b67fa2 100644 --- a/src/core/client/framework/lib/relay/withLocalStateContainer.tsx +++ b/src/core/client/framework/lib/relay/withLocalStateContainer.tsx @@ -13,6 +13,7 @@ import { GraphQLTaggedNode, } from "relay-runtime"; +import { _RefType } from "react-relay"; import { withContext } from "../bootstrap"; interface Props { @@ -35,9 +36,9 @@ export const LOCAL_ID = "client:root.local"; * The `fragmentSpec` must be a `Fragment` on the `LOCAL_TYPE` which * must have the `LOCAL_ID`. */ -function withLocalStateContainer( +function withLocalStateContainer( fragmentSpec: GraphQLTaggedNode -): InferableComponentEnhancer<{ local: T }> { +): InferableComponentEnhancer<{ local: _RefType }> { return compose( withContext(({ relayEnvironment }) => ({ relayEnvironment })), hoistStatics((BaseComponent: React.ComponentType) => { diff --git a/src/core/client/framework/lib/relay/withPaginationContainer.ts b/src/core/client/framework/lib/relay/withPaginationContainer.ts index bb2740d01..563a7fcb5 100644 --- a/src/core/client/framework/lib/relay/withPaginationContainer.ts +++ b/src/core/client/framework/lib/relay/withPaginationContainer.ts @@ -1,24 +1,62 @@ import { - ConnectionConfig, + ConnectionData, createPaginationContainer, + FragmentOrRegularProp, GraphQLTaggedNode, RelayPaginationProp, } from "react-relay"; -import { InferableComponentEnhancerWithProps } from "recompose"; +import { + InferableComponentEnhancerWithProps, + wrapDisplayName, +} from "recompose"; +import { Variables } from "relay-runtime"; + +import hideForwardRef from "./hideForwardRef"; +import { FragmentKeysNoLocal } from "./types"; + +// TODO: (cvle) at some point we might switch these to stock react-relay typings +// when they become versatile enough. +export type FragmentVariablesGetter = ( + prevVars: V, + totalCount: number +) => V; + +export interface ConnectionConfig< + P, + V extends Variables = Variables, + F extends Variables = Variables +> { + direction?: "backward" | "forward"; + getConnectionFromProps?(props: P): ConnectionData | undefined | null; + getFragmentVariables?: FragmentVariablesGetter; + getVariables( + props: P, + paginationInfo: { count: number; cursor?: string }, + fragmentVariables: F + ): V; + query: GraphQLTaggedNode; +} /** * withPaginationContainer is a curried version of `createPaginationContainers` * from Relay. */ -export default ( - fragmentSpec: { [P in keyof T]: GraphQLTaggedNode }, - connectionConfig: ConnectionConfig< - InnerProps, - FragmentVariables, - QueryVariables - > +export default ( + fragmentSpec: { [P in FragmentKeysNoLocal]: GraphQLTaggedNode } & { + _?: never; + }, + connectionConfig: ConnectionConfig ): InferableComponentEnhancerWithProps< - T & { relay: RelayPaginationProp }, - { [P in keyof T]: any } -> => (component: React.ComponentType) => - createPaginationContainer(component, fragmentSpec, connectionConfig) as any; + { [P in FragmentKeysNoLocal]: T[P] } & { relay: RelayPaginationProp }, + { [P in FragmentKeysNoLocal]: FragmentOrRegularProp } +> => (component: React.ComponentType) => { + const result = createPaginationContainer( + component, + fragmentSpec, + connectionConfig + ); + result.displayName = wrapDisplayName(component, "Relay"); + // TODO: (cvle) We wrap this currently to hide the ForwardRef which is not + // well supported yet in enzyme. + return hideForwardRef(result) as any; +}; diff --git a/src/core/client/framework/lib/relay/withRefetchContainer.ts b/src/core/client/framework/lib/relay/withRefetchContainer.ts index 9e9989758..3036daaf8 100644 --- a/src/core/client/framework/lib/relay/withRefetchContainer.ts +++ b/src/core/client/framework/lib/relay/withRefetchContainer.ts @@ -1,19 +1,33 @@ import { createRefetchContainer, + FragmentOrRegularProp, GraphQLTaggedNode, RelayRefetchProp, } from "react-relay"; -import { InferableComponentEnhancerWithProps } from "recompose"; +import { + InferableComponentEnhancerWithProps, + wrapDisplayName, +} from "recompose"; + +import hideForwardRef from "./hideForwardRef"; +import { FragmentKeysNoLocal } from "./types"; /** * withRefetchContainer is a curried version of `createRefetchContainers` * from Relay. */ export default ( - fragmentSpec: { [P in keyof T]: GraphQLTaggedNode }, + fragmentSpec: { [P in FragmentKeysNoLocal]: GraphQLTaggedNode } & { + _?: never; + }, refetchQuery: GraphQLTaggedNode ): InferableComponentEnhancerWithProps< - T & { relay: RelayRefetchProp }, - { [P in keyof T]: any } -> => (component: React.ComponentType) => - createRefetchContainer(component, fragmentSpec, refetchQuery) as any; + { [P in FragmentKeysNoLocal]: T[P] } & { relay: RelayRefetchProp }, + { [P in FragmentKeysNoLocal]: FragmentOrRegularProp } +> => (component: React.ComponentType) => { + const result = createRefetchContainer(component, fragmentSpec, refetchQuery); + result.displayName = wrapDisplayName(component, "Relay"); + // TODO: (cvle) We wrap this currently to hide the ForwardRef which is not + // well supported yet in enzyme. + return hideForwardRef(result) as any; +}; diff --git a/src/core/client/framework/testHelpers/createSinonStub.ts b/src/core/client/framework/testHelpers/createSinonStub.ts new file mode 100644 index 000000000..a1d92cddf --- /dev/null +++ b/src/core/client/framework/testHelpers/createSinonStub.ts @@ -0,0 +1,29 @@ +import sinon, { SinonStub } from "sinon"; + +/** + * createSinonStub assist in setting up a stub with different + * return paths. + * + * e.g. + * ``` + * const s = sinon.stub(); + * s.throws(); + * s.withArgs("a").returns(0); + * s.withArgs("b").returns(1); + * ``` + * is equivalent to. + * ``` + * const s = createSinonStub( + * s => s.throws(), + * s => s.withArgs("a").returns(0), + * s => s.withArgs("b").returns(1), + * ); + * ``` + */ +export default function createSinonStub( + ...callbacks: Array<(s: SinonStub) => void> +): SinonStub { + const stub = sinon.stub(); + callbacks.forEach(cb => cb(stub)); + return stub; +} diff --git a/src/core/client/framework/testHelpers/index.ts b/src/core/client/framework/testHelpers/index.ts index 182ba19fb..345e7be68 100644 --- a/src/core/client/framework/testHelpers/index.ts +++ b/src/core/client/framework/testHelpers/index.ts @@ -2,5 +2,9 @@ export { default as createRelayEnvironment, CreateRelayEnvironmentParams, } from "./createRelayEnvironment"; - export { default as createFluentBundle } from "./createFluentBundle"; +export { default as createSinonStub } from "./createSinonStub"; +export { + default as removeFragmentRefs, + NoFragmentRefs, +} from "./removeFragmentRefs"; diff --git a/src/core/client/framework/testHelpers/removeFragmentRefs.ts b/src/core/client/framework/testHelpers/removeFragmentRefs.ts new file mode 100644 index 000000000..b92e57aa9 --- /dev/null +++ b/src/core/client/framework/testHelpers/removeFragmentRefs.ts @@ -0,0 +1,16 @@ +import { ComponentType } from "react"; + +/** Remove all traces of `$fragmentRefs` and `$refType` from type recursively */ +export type NoFragmentRefs = T extends object + ? { + [P in Exclude]: NoFragmentRefs< + T[P] + > + } + : T; + +export default function removeFragmentRefs( + component: ComponentType +): ComponentType> { + return component as any; +} diff --git a/src/core/client/stream/components/App.tsx b/src/core/client/stream/components/App.tsx index a09c49149..b7d2d09d9 100644 --- a/src/core/client/stream/components/App.tsx +++ b/src/core/client/stream/components/App.tsx @@ -2,9 +2,9 @@ import * as React from "react"; import { StatelessComponent } from "react"; import { Flex } from "talk-ui/components"; + import PermalinkViewQuery from "../queries/PermalinkViewQuery"; import StreamQuery from "../queries/StreamQuery"; - import * as styles from "./App.css"; export interface AppProps { diff --git a/src/core/client/stream/components/PermalinkButton/PermalinkButton.tsx b/src/core/client/stream/components/PermalinkButton/PermalinkButton.tsx index 12540c1d3..cf0fad56c 100644 --- a/src/core/client/stream/components/PermalinkButton/PermalinkButton.tsx +++ b/src/core/client/stream/components/PermalinkButton/PermalinkButton.tsx @@ -1,5 +1,6 @@ import { Localized } from "fluent-react/compat"; import React from "react"; + import { oncePerFrame } from "talk-common/utils"; import { Button, diff --git a/src/core/client/stream/components/PermalinkView.tsx b/src/core/client/stream/components/PermalinkView.tsx index 362efa562..1747152d7 100644 --- a/src/core/client/stream/components/PermalinkView.tsx +++ b/src/core/client/stream/components/PermalinkView.tsx @@ -1,13 +1,14 @@ import { Localized } from "fluent-react/compat"; import React, { MouseEvent, StatelessComponent } from "react"; +import { PropTypesOf } from "talk-framework/types"; import { Button, Typography } from "talk-ui/components"; import CommentContainer from "../containers/CommentContainer"; import * as styles from "./PermalinkView.css"; export interface PermalinkViewProps { - comment: {} | null; + comment: PropTypesOf["data"] | null; showAllCommentsHref: string | null; onShowAllComments: (e: MouseEvent) => void; } diff --git a/src/core/client/stream/components/PostCommentForm.tsx b/src/core/client/stream/components/PostCommentForm.tsx index e1019ec84..00f544b86 100644 --- a/src/core/client/stream/components/PostCommentForm.tsx +++ b/src/core/client/stream/components/PostCommentForm.tsx @@ -27,13 +27,21 @@ export interface PostCommentFormProps { const PostCommentForm: StatelessComponent = props => (
{({ handleSubmit, submitting }) => ( - + {({ input, meta }) => (
- + Post a comment @@ -42,7 +50,7 @@ const PostCommentForm: StatelessComponent = props => ( attrs={{ placeholder: true }} > input.onChange(html)} value={input.value} placeholder="Post a comment" diff --git a/src/core/client/stream/components/ReplyList.spec.tsx b/src/core/client/stream/components/ReplyList.spec.tsx index 76750c8a0..04038673f 100644 --- a/src/core/client/stream/components/ReplyList.spec.tsx +++ b/src/core/client/stream/components/ReplyList.spec.tsx @@ -3,24 +3,27 @@ import { noop } from "lodash"; import React from "react"; import sinon, { SinonSpy } from "sinon"; +import { removeFragmentRefs } from "talk-framework/testHelpers"; import { PropTypesOf } from "talk-framework/types"; import ReplyList from "./ReplyList"; +const ReplyListN = removeFragmentRefs(ReplyList); + it("renders correctly", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { commentID: "comment-id", comments: [{ id: "comment-1" }, { id: "comment-2" }], onShowAll: noop, hasMore: false, disableShowAll: false, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); describe("when there is more", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { commentID: "comment-id", comments: [{ id: "comment-1" }, { id: "comment-2" }], onShowAll: sinon.spy(), @@ -28,7 +31,7 @@ describe("when there is more", () => { disableShowAll: false, }; - const wrapper = shallow(); + const wrapper = shallow(); it("renders a load more button", () => { expect(wrapper).toMatchSnapshot(); }); @@ -41,7 +44,7 @@ describe("when there is more", () => { }); const wrapperDisabledButton = shallow( - + ); it("disables load more button", () => { expect(wrapperDisabledButton).toMatchSnapshot(); diff --git a/src/core/client/stream/components/ReplyList.tsx b/src/core/client/stream/components/ReplyList.tsx index c35e0c32c..cd2be4660 100644 --- a/src/core/client/stream/components/ReplyList.tsx +++ b/src/core/client/stream/components/ReplyList.tsx @@ -2,6 +2,7 @@ import { Localized } from "fluent-react/compat"; import * as React from "react"; import { StatelessComponent } from "react"; +import { PropTypesOf } from "talk-framework/types"; import { Button, HorizontalGutter } from "talk-ui/components"; import CommentContainer from "../containers/CommentContainer"; @@ -9,7 +10,9 @@ import Indent from "./Indent"; export interface ReplyListProps { commentID: string; - comments: ReadonlyArray<{ id: string }>; + comments: ReadonlyArray< + { id: string } & PropTypesOf["data"] + >; onShowAll: () => void; hasMore: boolean; disableShowAll: boolean; diff --git a/src/core/client/stream/components/Stream.spec.tsx b/src/core/client/stream/components/Stream.spec.tsx index 14ad6dec5..ff327d798 100644 --- a/src/core/client/stream/components/Stream.spec.tsx +++ b/src/core/client/stream/components/Stream.spec.tsx @@ -3,12 +3,15 @@ import { noop } from "lodash"; import React from "react"; import sinon, { SinonSpy } from "sinon"; +import { removeFragmentRefs } from "talk-framework/testHelpers"; import { PropTypesOf } from "talk-framework/types"; import Stream from "./Stream"; +const StreamN = removeFragmentRefs(Stream); + it("renders correctly", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { assetID: "asset-id", isClosed: false, comments: [{ id: "comment-1" }, { id: "comment-2" }], @@ -17,13 +20,13 @@ it("renders correctly", () => { hasMore: false, user: null, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); describe("when use is logged in", () => { it("renders correctly", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { assetID: "asset-id", isClosed: false, comments: [{ id: "comment-1" }, { id: "comment-2" }], @@ -32,13 +35,13 @@ describe("when use is logged in", () => { hasMore: false, user: {}, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); describe("when there is more", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { assetID: "asset-id", isClosed: false, comments: [{ id: "comment-1" }, { id: "comment-2" }], @@ -48,7 +51,7 @@ describe("when there is more", () => { user: null, }; - const wrapper = shallow(); + const wrapper = shallow(); it("renders a load more button", () => { expect(wrapper).toMatchSnapshot(); }); @@ -58,7 +61,7 @@ describe("when there is more", () => { expect((props.onLoadMore as SinonSpy).calledOnce).toBe(true); }); - const wrapperDisabledButton = shallow(); + const wrapperDisabledButton = shallow(); it("disables load more button", () => { expect(wrapperDisabledButton).toMatchSnapshot(); }); diff --git a/src/core/client/stream/components/Stream.tsx b/src/core/client/stream/components/Stream.tsx index 884157e36..2d1358f64 100644 --- a/src/core/client/stream/components/Stream.tsx +++ b/src/core/client/stream/components/Stream.tsx @@ -2,6 +2,7 @@ import { Localized } from "fluent-react/compat"; import * as React from "react"; import { StatelessComponent } from "react"; +import { PropTypesOf } from "talk-framework/types"; import { Button, HorizontalGutter } from "talk-ui/components"; import CommentContainer from "../containers/CommentContainer"; @@ -14,11 +15,14 @@ import * as styles from "./Stream.css"; export interface StreamProps { assetID: string; isClosed?: boolean; - comments: ReadonlyArray<{ id: string }>; + comments: ReadonlyArray< + { id: string } & PropTypesOf["data"] & + PropTypesOf["comment"] + >; onLoadMore?: () => void; hasMore?: boolean; disableLoadMore?: boolean; - user: {} | null; + user: PropTypesOf["user"] | null; } const Stream: StatelessComponent = props => { diff --git a/src/core/client/stream/containers/AppContainer.tsx b/src/core/client/stream/containers/AppContainer.tsx index 7fbc7f6d8..533e8e37f 100644 --- a/src/core/client/stream/containers/AppContainer.tsx +++ b/src/core/client/stream/containers/AppContainer.tsx @@ -16,7 +16,7 @@ const AppContainer: StatelessComponent = ({ return ; }; -const enhanced = withLocalStateContainer( +const enhanced = withLocalStateContainer( graphql` fragment AppContainerLocal on Local { commentID diff --git a/src/core/client/stream/containers/CommentContainer.spec.tsx b/src/core/client/stream/containers/CommentContainer.spec.tsx index 44e4a0a19..892957994 100644 --- a/src/core/client/stream/containers/CommentContainer.spec.tsx +++ b/src/core/client/stream/containers/CommentContainer.spec.tsx @@ -1,12 +1,16 @@ import { shallow } from "enzyme"; import React from "react"; +import { removeFragmentRefs } from "talk-framework/testHelpers"; import { PropTypesOf } from "talk-framework/types"; import { CommentContainer } from "./CommentContainer"; +// Remove relay refs so we can stub the props. +const CommentContainerN = removeFragmentRefs(CommentContainer); + it("renders username and body", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { data: { id: "comment-id", author: { @@ -17,12 +21,12 @@ it("renders username and body", () => { }, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); it("renders body only", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { data: { id: "comment-id", author: { @@ -33,6 +37,6 @@ it("renders body only", () => { }, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/src/core/client/stream/containers/CommentContainer.tsx b/src/core/client/stream/containers/CommentContainer.tsx index 0316112d1..4c0a97744 100644 --- a/src/core/client/stream/containers/CommentContainer.tsx +++ b/src/core/client/stream/containers/CommentContainer.tsx @@ -28,7 +28,7 @@ export const CommentContainer: StatelessComponent = props => { return ; }; -const enhanced = withFragmentContainer<{ data: Data }>({ +const enhanced = withFragmentContainer({ data: graphql` fragment CommentContainer on Comment { ...CommentContainer_comment @relay(mask: false) diff --git a/src/core/client/stream/containers/PermalinkButtonContainer.tsx b/src/core/client/stream/containers/PermalinkButtonContainer.tsx index fd1509117..babb4197f 100644 --- a/src/core/client/stream/containers/PermalinkButtonContainer.tsx +++ b/src/core/client/stream/containers/PermalinkButtonContainer.tsx @@ -19,7 +19,7 @@ export const PermalinkContainer: StatelessComponent = ({ ) : null; }; -const enhanced = withLocalStateContainer( +const enhanced = withLocalStateContainer( graphql` fragment PermalinkButtonContainerLocal on Local { assetURL diff --git a/src/core/client/stream/containers/PermalinkViewContainer.tsx b/src/core/client/stream/containers/PermalinkViewContainer.tsx index 096785e27..29e08c61e 100644 --- a/src/core/client/stream/containers/PermalinkViewContainer.tsx +++ b/src/core/client/stream/containers/PermalinkViewContainer.tsx @@ -52,9 +52,7 @@ const enhanced = withContext(ctx => ({ pym: ctx.pym, }))( withSetCommentIDMutation( - withFragmentContainer<{ - comment: CommentData | null; - }>({ + withFragmentContainer({ comment: graphql` fragment PermalinkViewContainer_comment on Comment { ...CommentContainer diff --git a/src/core/client/stream/containers/PostCommentFormContainer.tsx b/src/core/client/stream/containers/PostCommentFormContainer.tsx index bf4ab8cf9..6398025eb 100644 --- a/src/core/client/stream/containers/PostCommentFormContainer.tsx +++ b/src/core/client/stream/containers/PostCommentFormContainer.tsx @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { Component, ReactNode } from "react"; import { BadUserInputError } from "talk-framework/lib/errors"; import { PropTypesOf } from "talk-framework/types"; @@ -11,6 +11,7 @@ import { CreateCommentMutation, withCreateCommentMutation } from "../mutations"; interface InnerProps { createComment: CreateCommentMutation; assetID: string; + children?: ReactNode; } class PostCommentFormContainer extends Component { diff --git a/src/core/client/stream/containers/ReplyListContainer.spec.tsx b/src/core/client/stream/containers/ReplyListContainer.spec.tsx index 3a414dd51..9096f3614 100644 --- a/src/core/client/stream/containers/ReplyListContainer.spec.tsx +++ b/src/core/client/stream/containers/ReplyListContainer.spec.tsx @@ -2,13 +2,17 @@ import { shallow, ShallowWrapper } from "enzyme"; import { noop } from "lodash"; import React from "react"; +import { removeFragmentRefs } from "talk-framework/testHelpers"; import { PropTypesOf } from "talk-framework/types"; import ReplyList from "../components/ReplyList"; import { ReplyListContainer } from "./ReplyListContainer"; +// Remove relay refs so we can stub the props. +const ReplyListContainerN = removeFragmentRefs(ReplyListContainer); + it("renders correctly", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { comment: { id: "comment-id", replies: { @@ -20,12 +24,12 @@ it("renders correctly", () => { isLoading: noop, } as any, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); it("renders correctly when replies are null", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { comment: { id: "comment-id", replies: null, @@ -35,13 +39,13 @@ it("renders correctly when replies are null", () => { isLoading: noop, } as any, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); describe("when has more replies", () => { let finishLoading: ((error?: Error) => void) | null = null; - const props: PropTypesOf = { + const props: PropTypesOf = { comment: { id: "comment-id", replies: { @@ -57,7 +61,7 @@ describe("when has more replies", () => { let wrapper: ShallowWrapper; - beforeAll(() => (wrapper = shallow())); + beforeAll(() => (wrapper = shallow())); it("renders hasMore", () => { expect(wrapper).toMatchSnapshot(); diff --git a/src/core/client/stream/containers/ReplyListContainer.tsx b/src/core/client/stream/containers/ReplyListContainer.tsx index 48db9645a..de741c60d 100644 --- a/src/core/client/stream/containers/ReplyListContainer.tsx +++ b/src/core/client/stream/containers/ReplyListContainer.tsx @@ -23,7 +23,7 @@ export class ReplyListContainer extends React.Component { public render() { if ( - this.props.comment.replies === null || + this.props.comment.replies == null || this.props.comment.replies.edges.length === 0 ) { return null; @@ -67,10 +67,9 @@ interface FragmentVariables { } const enhanced = withPaginationContainer< - { comment: Data }, InnerProps, - FragmentVariables, - ReplyListContainerPaginationQueryVariables + ReplyListContainerPaginationQueryVariables, + FragmentVariables >( { comment: graphql` diff --git a/src/core/client/stream/containers/StreamContainer.spec.tsx b/src/core/client/stream/containers/StreamContainer.spec.tsx index 2e19bda86..531c74970 100644 --- a/src/core/client/stream/containers/StreamContainer.spec.tsx +++ b/src/core/client/stream/containers/StreamContainer.spec.tsx @@ -2,13 +2,17 @@ import { shallow, ShallowWrapper } from "enzyme"; import { noop } from "lodash"; import React from "react"; +import { removeFragmentRefs } from "talk-framework/testHelpers"; import { PropTypesOf } from "talk-framework/types"; import Stream from "../components/Stream"; import { StreamContainer } from "./StreamContainer"; +// Remove relay refs so we can stub the props. +const StreamContainerN = removeFragmentRefs(StreamContainer); + it("renders correctly", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { asset: { id: "asset-id", isClosed: false, @@ -16,18 +20,19 @@ it("renders correctly", () => { edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }], }, }, + user: null, relay: { hasMore: noop, isLoading: noop, } as any, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); describe("when has more comments", () => { let finishLoading: ((error?: Error) => void) | null = null; - const props: PropTypesOf = { + const props: PropTypesOf = { asset: { id: "asset-id", isClosed: false, @@ -35,6 +40,7 @@ describe("when has more comments", () => { edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }], }, }, + user: null, relay: { hasMore: () => true, isLoading: () => false, @@ -44,7 +50,7 @@ describe("when has more comments", () => { let wrapper: ShallowWrapper; - beforeAll(() => (wrapper = shallow())); + beforeAll(() => (wrapper = shallow())); it("renders hasMore", () => { expect(wrapper).toMatchSnapshot(); diff --git a/src/core/client/stream/containers/StreamContainer.tsx b/src/core/client/stream/containers/StreamContainer.tsx index d629c35d1..ff24de3fb 100644 --- a/src/core/client/stream/containers/StreamContainer.tsx +++ b/src/core/client/stream/containers/StreamContainer.tsx @@ -64,10 +64,9 @@ interface FragmentVariables { } const enhanced = withPaginationContainer< - { asset: AssetData; user: UserData | null }, InnerProps, - FragmentVariables, - StreamContainerPaginationQueryVariables + StreamContainerPaginationQueryVariables, + FragmentVariables >( { asset: graphql` diff --git a/src/core/client/stream/containers/UserBoxContainer.spec.tsx b/src/core/client/stream/containers/UserBoxContainer.spec.tsx index d40d0878a..f99832b8e 100644 --- a/src/core/client/stream/containers/UserBoxContainer.spec.tsx +++ b/src/core/client/stream/containers/UserBoxContainer.spec.tsx @@ -1,12 +1,16 @@ import { shallow } from "enzyme"; import React from "react"; +import { removeFragmentRefs } from "talk-framework/testHelpers"; import { PropTypesOf } from "talk-framework/types"; import { UserBoxContainer } from "./UserBoxContainer"; +// Remove relay refs so we can stub the props. +const UserBoxContainerN = removeFragmentRefs(UserBoxContainer); + it("renders correctly", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { local: { authPopup: { open: false, @@ -22,6 +26,6 @@ it("renders correctly", () => { // tslint:disable-next-line:no-empty signOut: async () => {}, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/src/core/client/stream/containers/UserBoxContainer.tsx b/src/core/client/stream/containers/UserBoxContainer.tsx index cc05e3872..6a390c63b 100644 --- a/src/core/client/stream/containers/UserBoxContainer.tsx +++ b/src/core/client/stream/containers/UserBoxContainer.tsx @@ -78,7 +78,7 @@ export class UserBoxContainer extends Component { const enhanced = withSignOutMutation( withSetAuthPopupStateMutation( withShowAuthPopupMutation( - withLocalStateContainer( + withLocalStateContainer( graphql` fragment UserBoxContainerLocal on Local { authPopup { @@ -89,7 +89,7 @@ const enhanced = withSignOutMutation( } ` )( - withFragmentContainer<{ user: UserData | null }>({ + withFragmentContainer({ user: graphql` fragment UserBoxContainer_user on User { username diff --git a/src/core/client/stream/containers/__snapshots__/StreamContainer.spec.tsx.snap b/src/core/client/stream/containers/__snapshots__/StreamContainer.spec.tsx.snap index e3813397c..e24710a2b 100644 --- a/src/core/client/stream/containers/__snapshots__/StreamContainer.spec.tsx.snap +++ b/src/core/client/stream/containers/__snapshots__/StreamContainer.spec.tsx.snap @@ -16,6 +16,7 @@ exports[`renders correctly 1`] = ` disableLoadMore={false} isClosed={false} onLoadMore={[Function]} + user={null} /> `; @@ -36,6 +37,7 @@ exports[`when has more comments renders hasMore 1`] = ` hasMore={true} isClosed={false} onLoadMore={[Function]} + user={null} /> `; @@ -56,6 +58,7 @@ exports[`when has more comments when loading more disables load more button 1`] hasMore={true} isClosed={false} onLoadMore={[Function]} + user={null} /> `; @@ -76,5 +79,6 @@ exports[`when has more comments when loading more enable load more button after hasMore={true} isClosed={false} onLoadMore={[Function]} + user={null} /> `; diff --git a/src/core/client/stream/mutations/CreateCommentMutation.ts b/src/core/client/stream/mutations/CreateCommentMutation.ts index a6ac895e4..079285b91 100644 --- a/src/core/client/stream/mutations/CreateCommentMutation.ts +++ b/src/core/client/stream/mutations/CreateCommentMutation.ts @@ -1,30 +1,35 @@ import { graphql } from "react-relay"; import { Environment } from "relay-runtime"; +import uuid from "uuid/v4"; +import { getMe } from "talk-framework/helpers"; import { commitMutationPromiseNormalized, createMutationContainer, } from "talk-framework/lib/relay"; import { Omit } from "talk-framework/types"; -import { - CreateCommentMutationResponse, - CreateCommentMutationVariables, -} from "talk-stream/__generated__/CreateCommentMutation.graphql"; + +import { CreateCommentMutation as MutationTypes } from "talk-stream/__generated__/CreateCommentMutation.graphql"; export type CreateCommentInput = Omit< - CreateCommentMutationVariables["input"], + MutationTypes["variables"]["input"], "clientMutationId" >; const mutation = graphql` mutation CreateCommentMutation($input: CreateCommentInput!) { createComment(input: $input) { - comment { - id - author { - username + commentEdge { + cursor + node { + id + author { + id + username + } + body + createdAt } - body } clientMutationId } @@ -34,30 +39,47 @@ const mutation = graphql` let clientMutationId = 0; function commit(environment: Environment, input: CreateCommentInput) { - return commitMutationPromiseNormalized< - CreateCommentMutationResponse["createComment"], - CreateCommentMutationVariables - >(environment, { + const me = getMe(environment)!; + const currentDate = new Date().toISOString(); + return commitMutationPromiseNormalized(environment, { mutation, variables: { input: { ...input, + clientMutationId: clientMutationId.toString(), + }, + }, + optimisticResponse: { + createComment: { + commentEdge: { + cursor: currentDate, + node: { + id: uuid(), + createdAt: currentDate, + author: { + id: me.id, + username: me.username, + }, + body: input.body, + }, + }, clientMutationId: (clientMutationId++).toString(), }, }, - updater: store => { - const payload = store.getRootField("createComment"); - if (payload) { - const newRecord = payload.getLinkedRecord("comment")!; - const root = store.getRoot(); - const records = root.getLinkedRecords("comments"); - if (!records) { - throw new Error("Unexpected cache state"); - } - - root.setLinkedRecords([...records, newRecord], "comments"); - } - }, + configs: [ + { + type: "RANGE_ADD", + connectionInfo: [ + { + key: "Stream_comments", + rangeBehavior: "prepend", + filters: { orderBy: "CREATED_AT_DESC" }, + }, + ], + parentID: input.assetID, + edgeName: "commentEdge", + }, + ], }); } @@ -68,4 +90,4 @@ export const withCreateCommentMutation = createMutationContainer( export type CreateCommentMutation = ( input: CreateCommentInput -) => Promise; +) => Promise; diff --git a/src/core/client/stream/queries/PermalinkViewQuery.tsx b/src/core/client/stream/queries/PermalinkViewQuery.tsx index 3a9b22f71..32943ea17 100644 --- a/src/core/client/stream/queries/PermalinkViewQuery.tsx +++ b/src/core/client/stream/queries/PermalinkViewQuery.tsx @@ -7,10 +7,7 @@ import { QueryRenderer, withLocalStateContainer, } from "talk-framework/lib/relay"; -import { - PermalinkViewQueryResponse, - PermalinkViewQueryVariables, -} from "talk-stream/__generated__/PermalinkViewQuery.graphql"; +import { PermalinkViewQuery as QueryTypes } from "talk-stream/__generated__/PermalinkViewQuery.graphql"; import { PermalinkViewQueryLocal as Local } from "talk-stream/__generated__/PermalinkViewQueryLocal.graphql"; import { Spinner } from "talk-ui/components"; @@ -23,7 +20,7 @@ interface InnerProps { export const render = ({ error, props, -}: ReadyState) => { +}: ReadyState) => { if (error) { return
{error.message}
; } @@ -36,7 +33,7 @@ export const render = ({ const PermalinkViewQuery: StatelessComponent = ({ local: { commentID }, }) => ( - + query={graphql` query PermalinkViewQuery($commentID: ID!) { comment(id: $commentID) { @@ -45,13 +42,13 @@ const PermalinkViewQuery: StatelessComponent = ({ } `} variables={{ - commentID, + commentID: commentID!, }} render={render} /> ); -const enhanced = withLocalStateContainer( +const enhanced = withLocalStateContainer( graphql` fragment PermalinkViewQueryLocal on Local { commentID diff --git a/src/core/client/stream/queries/StreamQuery.tsx b/src/core/client/stream/queries/StreamQuery.tsx index 41d262428..67a5ae012 100644 --- a/src/core/client/stream/queries/StreamQuery.tsx +++ b/src/core/client/stream/queries/StreamQuery.tsx @@ -1,3 +1,4 @@ +import { Localized } from "fluent-react/compat"; import React, { StatelessComponent } from "react"; import { ReadyState } from "react-relay"; import { @@ -5,10 +6,7 @@ import { QueryRenderer, withLocalStateContainer, } from "talk-framework/lib/relay"; -import { - StreamQueryResponse, - StreamQueryVariables, -} from "talk-stream/__generated__/StreamQuery.graphql"; +import { StreamQuery as QueryTypes } from "talk-stream/__generated__/StreamQuery.graphql"; import { StreamQueryLocal as Local } from "talk-stream/__generated__/StreamQueryLocal.graphql"; import { Spinner } from "talk-ui/components"; import StreamContainer from "../containers/StreamContainer"; @@ -17,12 +15,22 @@ interface InnerProps { local: Local; } -export const render = ({ error, props }: ReadyState) => { +export const render = ({ + error, + props, +}: ReadyState) => { if (error) { return
{error.message}
; } if (props) { + if (!props.asset) { + return ( + +
Asset not found
+
+ ); + } return ; } @@ -32,7 +40,7 @@ export const render = ({ error, props }: ReadyState) => { const StreamQuery: StatelessComponent = ({ local: { assetID, authRevision }, }) => ( - + query={graphql` query StreamQuery($assetID: ID!, $authRevision: Int!) { asset(id: $assetID) { @@ -47,14 +55,14 @@ const StreamQuery: StatelessComponent = ({ } `} variables={{ - assetID, + assetID: assetID!, authRevision, }} render={render} /> ); -const enhanced = withLocalStateContainer( +const enhanced = withLocalStateContainer( graphql` fragment StreamQueryLocal on Local { assetID diff --git a/src/core/client/stream/test/__snapshots__/postComment.spec.tsx.snap b/src/core/client/stream/test/__snapshots__/postComment.spec.tsx.snap new file mode 100644 index 000000000..cb4c00a5c --- /dev/null +++ b/src/core/client/stream/test/__snapshots__/postComment.spec.tsx.snap @@ -0,0 +1,847 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`post a comment 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+ +
+
+ +
+
+
+ + + +
+
Hello world!", + } + } + id="comments-postCommentForm-field" + onBlur={[Function]} + onChange={[Function]} + onCut={[Function]} + onFocus={[Function]} + onInput={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onSelect={[Function]} + /> +
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+ + Markus + + +
+
Hello world!", + } + } + /> +
+
+
+
+
+
+ + Markus + + +
+
+
+
+
+
+
+
+ + Lukas + + +
+
+
+
+
+
+
+
+`; + +exports[`post a comment 2`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+ + Markus + + +
+
Hello world! (from server)", + } + } + /> +
+
+
+
+
+
+ + Markus + + +
+
+
+
+
+
+
+
+ + Lukas + + +
+
+
+
+
+
+
+
+`; + +exports[`renders comment stream 1`] = ` +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+
+ + Powered by + + ⁨The Coral Project⁩ + + +
+ +
+
+ +
+
+
+
+
+ + Markus + + +
+
+
+
+
+
+
+
+ + Lukas + + +
+
+
+
+
+
+
+
+`; diff --git a/src/core/client/stream/test/loadMore.spec.tsx b/src/core/client/stream/test/loadMore.spec.tsx index 5341b39ee..f58a80004 100644 --- a/src/core/client/stream/test/loadMore.spec.tsx +++ b/src/core/client/stream/test/loadMore.spec.tsx @@ -1,12 +1,12 @@ import React from "react"; import TestRenderer, { ReactTestRenderer } from "react-test-renderer"; -import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createSinonStub } from "talk-framework/testHelpers"; import AppContainer from "talk-stream/containers/AppContainer"; import createEnvironment from "./createEnvironment"; @@ -16,54 +16,55 @@ import { assets, comments } from "./fixtures"; let testRenderer: ReactTestRenderer; beforeEach(() => { - const connectionStub = sinon.stub().throws(); - connectionStub.withArgs({ first: 5, orderBy: "CREATED_AT_DESC" }).returns({ - edges: [ - { - node: comments[0], - cursor: comments[0].createdAt, - }, - { - node: comments[1], - cursor: comments[1].createdAt, - }, - ], - pageInfo: { - endCursor: comments[1].createdAt, - hasNextPage: true, - }, - }); - connectionStub - .withArgs({ - first: 10, - orderBy: "CREATED_AT_DESC", - after: comments[1].createdAt, - }) - .returns({ - edges: [ - { - node: comments[2], - cursor: comments[2].createdAt, - }, - ], - pageInfo: { - endCursor: comments[2].createdAt, - hasNextPage: false, - }, - }); - const assetStub = { ...assets[0], - comments: connectionStub, + comments: createSinonStub( + s => s.throws(), + s => + s.withArgs({ first: 5, orderBy: "CREATED_AT_DESC" }).returns({ + edges: [ + { + node: comments[0], + cursor: comments[0].createdAt, + }, + { + node: comments[1], + cursor: comments[1].createdAt, + }, + ], + pageInfo: { + endCursor: comments[1].createdAt, + hasNextPage: true, + }, + }), + s => + s + .withArgs({ + first: 10, + orderBy: "CREATED_AT_DESC", + after: comments[1].createdAt, + }) + .returns({ + edges: [ + { + node: comments[2], + cursor: comments[2].createdAt, + }, + ], + pageInfo: { + endCursor: comments[2].createdAt, + hasNextPage: false, + }, + }) + ), }; const resolvers = { Query: { - asset: sinon - .stub() - .throws() - .withArgs(undefined, { id: assetStub.id }) - .returns(assetStub), + asset: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: assetStub.id }).returns(assetStub) + ), }, }; diff --git a/src/core/client/stream/test/permalinkView.spec.tsx b/src/core/client/stream/test/permalinkView.spec.tsx index 666f4af5e..c68d3fde3 100644 --- a/src/core/client/stream/test/permalinkView.spec.tsx +++ b/src/core/client/stream/test/permalinkView.spec.tsx @@ -8,6 +8,7 @@ import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createSinonStub } from "talk-framework/testHelpers"; import AppContainer from "talk-stream/containers/AppContainer"; import createEnvironment from "./createEnvironment"; @@ -38,16 +39,14 @@ beforeEach(() => { const resolvers = { Query: { - comment: sinon - .stub() - .throws() - .withArgs(undefined, { id: commentStub.id }) - .returns(commentStub), - asset: sinon - .stub() - .throws() - .withArgs(undefined, { id: assetStub.id }) - .returns(assetStub), + comment: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: commentStub.id }).returns(commentStub) + ), + asset: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: assetStub.id }).returns(assetStub) + ), }, }; diff --git a/src/core/client/stream/test/permalinkViewCommentNotFound.spec.tsx b/src/core/client/stream/test/permalinkViewCommentNotFound.spec.tsx index 368e29bc3..f49dc0534 100644 --- a/src/core/client/stream/test/permalinkViewCommentNotFound.spec.tsx +++ b/src/core/client/stream/test/permalinkViewCommentNotFound.spec.tsx @@ -8,6 +8,7 @@ import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createSinonStub } from "talk-framework/testHelpers"; import AppContainer from "talk-stream/containers/AppContainer"; import createEnvironment from "./createEnvironment"; @@ -39,11 +40,10 @@ beforeEach(() => { const resolvers = { Query: { comment: () => null, - asset: sinon - .stub() - .throws() - .withArgs(undefined, { id: assetStub.id }) - .returns(assetStub), + asset: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: assetStub.id }).returns(assetStub) + ), }, }; diff --git a/src/core/client/stream/test/postComment.spec.tsx b/src/core/client/stream/test/postComment.spec.tsx new file mode 100644 index 000000000..9ddd8b1ec --- /dev/null +++ b/src/core/client/stream/test/postComment.spec.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import TestRenderer, { ReactTestRenderer } from "react-test-renderer"; +import { RecordProxy } from "relay-runtime"; +import timekeeper from "timekeeper"; + +import { timeout } from "talk-common/utils"; +import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; +import { PostMessageService } from "talk-framework/lib/postMessage"; +import { RestClient } from "talk-framework/lib/rest"; +import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createSinonStub } from "talk-framework/testHelpers"; +import AppContainer from "talk-stream/containers/AppContainer"; + +import createEnvironment from "./createEnvironment"; +import createFluentBundle from "./createFluentBundle"; +import createNodeMock from "./createNodeMock"; +import { assets, users } from "./fixtures"; + +let testRenderer: ReactTestRenderer; +beforeEach(() => { + const resolvers = { + Query: { + asset: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0]) + ), + me: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { clientAuthRevision: 0 }).returns(users[0]) + ), + }, + Mutation: { + createComment: createSinonStub( + s => s.throws(), + s => + s + .withArgs(undefined, { + input: { + assetID: assets[0].id, + body: "Hello world!", + clientMutationId: "0", + }, + }) + .returns({ + commentEdge: { + cursor: "2018-07-06T18:24:00.000Z", + node: { + id: "comment-x", + author: users[0], + body: "Hello world! (from server)", + createdAt: "2018-07-06T18:24:00.000Z", + }, + }, + clientMutationId: "0", + }) + ), + }, + }; + + const environment = createEnvironment({ + // Set this to true, to see graphql responses. + logNetwork: false, + resolvers, + initLocalState: (localRecord: RecordProxy) => { + localRecord.setValue(assets[0].id, "assetID"); + localRecord.setValue(0, "authRevision"); + }, + }); + + const context: TalkContext = { + relayEnvironment: environment, + localeBundles: [createFluentBundle()], + localStorage: createInMemoryStorage(), + sessionStorage: createInMemoryStorage(), + rest: new RestClient("http://localhost/api"), + postMessage: new PostMessageService(), + }; + + testRenderer = TestRenderer.create( + + + , + { createNodeMock } + ); +}); + +it("renders comment stream", async () => { + // Wait for loading. + await timeout(); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("post a comment", async () => { + testRenderer.root + .findByProps({ inputId: "comments-postCommentForm-field" }) + .props.onChange({ html: "Hello world!" }); + + timekeeper.travel(new Date("2018-07-06T18:24:00.000Z")); + testRenderer.root + .findByProps({ id: "comments-postCommentForm-form" }) + .props.onSubmit(); + // Test optimistic response. + expect(testRenderer.toJSON()).toMatchSnapshot(); + timekeeper.reset(); + + // Wait for loading. + await timeout(); + // Test after server response. + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/test/renderReplies.spec.tsx b/src/core/client/stream/test/renderReplies.spec.tsx index 673f6da60..970d1a31b 100644 --- a/src/core/client/stream/test/renderReplies.spec.tsx +++ b/src/core/client/stream/test/renderReplies.spec.tsx @@ -1,13 +1,13 @@ import React from "react"; import TestRenderer, { ReactTestRenderer } from "react-test-renderer"; import { RecordProxy } from "relay-runtime"; -import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createSinonStub } from "talk-framework/testHelpers"; import AppContainer from "talk-stream/containers/AppContainer"; import createEnvironment from "./createEnvironment"; @@ -19,11 +19,13 @@ let testRenderer: ReactTestRenderer; beforeEach(() => { const resolvers = { Query: { - asset: sinon - .stub() - .throws() - .withArgs(undefined, { id: assetWithReplies.id }) - .returns(assetWithReplies), + asset: createSinonStub( + s => s.throws(), + s => + s + .withArgs(undefined, { id: assetWithReplies.id }) + .returns(assetWithReplies) + ), }, }; diff --git a/src/core/client/stream/test/renderStream.spec.tsx b/src/core/client/stream/test/renderStream.spec.tsx index 3e95fbc25..3ea0d74c6 100644 --- a/src/core/client/stream/test/renderStream.spec.tsx +++ b/src/core/client/stream/test/renderStream.spec.tsx @@ -1,13 +1,13 @@ import React from "react"; import TestRenderer, { ReactTestRenderer } from "react-test-renderer"; import { RecordProxy } from "relay-runtime"; -import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createSinonStub } from "talk-framework/testHelpers"; import AppContainer from "talk-stream/containers/AppContainer"; import createEnvironment from "./createEnvironment"; @@ -19,11 +19,10 @@ let testRenderer: ReactTestRenderer; beforeEach(() => { const resolvers = { Query: { - asset: sinon - .stub() - .throws() - .withArgs(undefined, { id: assets[0].id }) - .returns(assets[0]), + asset: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0]) + ), }, }; diff --git a/src/core/client/stream/test/showAllReplies.spec.tsx b/src/core/client/stream/test/showAllReplies.spec.tsx index 052e38792..a76aab40d 100644 --- a/src/core/client/stream/test/showAllReplies.spec.tsx +++ b/src/core/client/stream/test/showAllReplies.spec.tsx @@ -8,6 +8,7 @@ import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createSinonStub } from "talk-framework/testHelpers"; import AppContainer from "talk-stream/containers/AppContainer"; import createEnvironment from "./createEnvironment"; @@ -17,41 +18,43 @@ import { assets, comments } from "./fixtures"; let testRenderer: ReactTestRenderer; beforeEach(() => { - const connectionStub = sinon.stub().throws(); - connectionStub.withArgs({ first: 5, orderBy: "CREATED_AT_ASC" }).returns({ - edges: [ - { - node: comments[1], - cursor: comments[1].createdAt, - }, - ], - pageInfo: { - endCursor: comments[1].createdAt, - hasNextPage: true, - }, - }); - connectionStub - .withArgs({ - first: sinon.match(n => n > 10000), - orderBy: "CREATED_AT_ASC", - after: comments[1].createdAt, - }) - .returns({ - edges: [ - { - node: comments[2], - cursor: comments[2].createdAt, - }, - ], - pageInfo: { - endCursor: comments[2].createdAt, - hasNextPage: false, - }, - }); - const commentStub = { ...comments[0], - replies: connectionStub, + replies: createSinonStub( + s => s.throws(), + s => + s.withArgs({ first: 5, orderBy: "CREATED_AT_ASC" }).returns({ + edges: [ + { + node: comments[1], + cursor: comments[1].createdAt, + }, + ], + pageInfo: { + endCursor: comments[1].createdAt, + hasNextPage: true, + }, + }), + s => + s + .withArgs({ + first: sinon.match(n => n > 10000), + orderBy: "CREATED_AT_ASC", + after: comments[1].createdAt, + }) + .returns({ + edges: [ + { + node: comments[2], + cursor: comments[2].createdAt, + }, + ], + pageInfo: { + endCursor: comments[2].createdAt, + hasNextPage: false, + }, + }) + ), }; const assetStub = { @@ -71,16 +74,14 @@ beforeEach(() => { const resolvers = { Query: { - comment: sinon - .stub() - .throws() - .withArgs(undefined, { id: commentStub.id }) - .returns(commentStub), - asset: sinon - .stub() - .throws() - .withArgs(undefined, { id: assetStub.id }) - .returns(assetStub), + comment: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: commentStub.id }).returns(commentStub) + ), + asset: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: assetStub.id }).returns(assetStub) + ), }, }; diff --git a/src/core/client/test/jsdom.ts b/src/core/client/test/jsdom.ts index 9e5518731..5ec90c988 100644 --- a/src/core/client/test/jsdom.ts +++ b/src/core/client/test/jsdom.ts @@ -5,6 +5,15 @@ declare var global: any; const jsdom = new JSDOM(""); const { window } = jsdom; +// tiny shim for getSelection for the RTE. +// tslint:disable:no-empty +window.getSelection = () => + ({ + addRange() {}, + removeAllRanges() {}, + } as any); +// tslint:enable:no-empty + function copyProps(src: any, target: any) { const props = Object.getOwnPropertyNames(src) .filter(prop => typeof target[prop] === "undefined") diff --git a/src/core/server/graph/tenant/resolvers/mutation.ts b/src/core/server/graph/tenant/resolvers/mutation.ts index 8f15117c9..1fb331602 100644 --- a/src/core/server/graph/tenant/resolvers/mutation.ts +++ b/src/core/server/graph/tenant/resolvers/mutation.ts @@ -1,10 +1,17 @@ import { GQLMutationTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; const Mutation: GQLMutationTypeResolver = { - createComment: async (source, { input }, ctx) => ({ - comment: await ctx.mutators.Comment.create(input), - clientMutationId: input.clientMutationId, - }), + createComment: async (source, { input }, ctx) => { + const comment = await ctx.mutators.Comment.create(input); + // TODO: (cvle) tell wyatt to take a look at this :-) + return { + commentEdge: { + cursor: comment.created_at, + node: comment, + }, + clientMutationId: input.clientMutationId, + }; + }, updateSettings: async (source, { input }, ctx) => ({ settings: await ctx.mutators.Settings.update(input.settings), clientMutationId: input.clientMutationId, diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index d51f4be16..d32d6259f 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -577,7 +577,7 @@ type Comment { """ createdAt is the date in which the comment was created. """ - createdAt: Time + createdAt: Time! """ author is the User that authored the Comment. @@ -821,9 +821,9 @@ mutation. """ type CreateCommentPayload { """ - comment is the possibly created comment. + CommentEdge is the possibly created comment edge. """ - comment: Comment + commentEdge: CommentEdge """ clientMutationId is required for Relay support. diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index 159ea0b3b..009c52e99 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -2,6 +2,8 @@ ## Comments Tab +comments-streamQuery-assetNotFound = Asset not found + comments-postCommentForm-submit = Submit comments-stream-loadMore = Load more comments-replyList-showAll = Show all