Merge pull request #1837 from coralproject/relay-1.7

[next] Implement PostComment Mutation, Upgrade Relay
This commit is contained in:
Kim Gardner
2018-08-31 13:49:14 -04:00
committed by GitHub
54 changed files with 1758 additions and 433 deletions
+3
View File
@@ -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
+286 -161
View File
@@ -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": {
+13 -12
View File
@@ -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",
-2
View File
@@ -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"
@@ -14,7 +14,7 @@ const AppContainer: StatelessComponent<InnerProps> = ({ local: { view } }) => {
return <App view={view} />;
};
const enhanced = withLocalStateContainer<Local>(
const enhanced = withLocalStateContainer(
graphql`
fragment AppContainerLocal on Local {
view
@@ -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)!;
}
@@ -0,0 +1 @@
export { default as getMe } from "./getMe";
@@ -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<V, R> = Omit<
QueryRendererPropsOrig<V, R>,
"environment"
>;
export type QueryRendererProps<
T extends OperationBase = OperationDefaults
> = Omit<QueryRendererPropsOrig<T>, "environment">;
/**
* TalkQueryRenderer is a wrappper around Relay's `QueryRenderer`.
* It supplies the `environment` from the context and has better
* generics type support.
*/
class TalkQueryRenderer<V, R> extends Component<QueryRendererProps<V, R>> {
class TalkQueryRenderer<
T extends OperationBase = OperationDefaults
> extends Component<QueryRendererProps<T>> {
public render() {
return (
<TalkContextConsumer>
@@ -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<T, U> = Omit<
MutationConfig<T, U>,
export type MutationPromiseConfig<T extends OperationBase> = Omit<
MutationConfig<T>,
"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<R, V>(
export async function commitMutationPromiseNormalized<T extends OperationBase>(
environment: Environment,
config: MutationPromiseConfig<R, V>
): Promise<R> {
config: MutationPromiseConfig<T>
): Promise<T["response"][keyof T["response"]]> {
try {
const response = await commitMutationPromise(environment, config);
return getPayload(response);
@@ -42,10 +42,10 @@ export async function commitMutationPromiseNormalized<R, V>(
/**
* Like `commitMutation` of the Relay API but returns a Promise.
*/
export function commitMutationPromise<R, V>(
export function commitMutationPromise<T extends OperationBase>(
environment: Environment,
config: MutationPromiseConfig<R, V>
): Promise<R> {
config: MutationPromiseConfig<T>
): Promise<T["response"][keyof T["response"]]> {
return new Promise((resolve, reject) => {
commitMutation(environment, {
...config,
@@ -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;
}
@@ -0,0 +1,7 @@
import { _RefType } from "react-relay";
export type FragmentKeys<T> = {
[P in keyof T]: T[P] extends _RefType<any> | null ? P : never
}[keyof T];
export type FragmentKeysNoLocal<T> = Exclude<FragmentKeys<T>, "local">;
@@ -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 <T>(
fragmentSpec: { [P in keyof T]: GraphQLTaggedNode }
): InferableComponentEnhancerWithProps<T, { [P in keyof T]: any }> => (
component: React.ComponentType<any>
) => createFragmentContainer(component, fragmentSpec) as any;
fragmentSpec: { [P in FragmentKeysNoLocal<T>]: GraphQLTaggedNode } & {
_?: never;
}
): InferableComponentEnhancerWithProps<
{ [P in FragmentKeysNoLocal<T>]: T[P] },
{ [P in FragmentKeysNoLocal<T>]: FragmentOrRegularProp<T[P]> }
> => (component: React.ComponentType<any>) => {
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;
};
@@ -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<T>(
function withLocalStateContainer(
fragmentSpec: GraphQLTaggedNode
): InferableComponentEnhancer<{ local: T }> {
): InferableComponentEnhancer<{ local: _RefType<any> }> {
return compose(
withContext(({ relayEnvironment }) => ({ relayEnvironment })),
hoistStatics((BaseComponent: React.ComponentType<any>) => {
@@ -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<V extends Variables = Variables> = (
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<F>;
getVariables(
props: P,
paginationInfo: { count: number; cursor?: string },
fragmentVariables: F
): V;
query: GraphQLTaggedNode;
}
/**
* withPaginationContainer is a curried version of `createPaginationContainers`
* from Relay.
*/
export default <T, InnerProps, FragmentVariables, QueryVariables>(
fragmentSpec: { [P in keyof T]: GraphQLTaggedNode },
connectionConfig: ConnectionConfig<
InnerProps,
FragmentVariables,
QueryVariables
>
export default <T, QueryVariables, FragmentVariables>(
fragmentSpec: { [P in FragmentKeysNoLocal<T>]: GraphQLTaggedNode } & {
_?: never;
},
connectionConfig: ConnectionConfig<T, QueryVariables, FragmentVariables>
): InferableComponentEnhancerWithProps<
T & { relay: RelayPaginationProp },
{ [P in keyof T]: any }
> => (component: React.ComponentType<any>) =>
createPaginationContainer(component, fragmentSpec, connectionConfig) as any;
{ [P in FragmentKeysNoLocal<T>]: T[P] } & { relay: RelayPaginationProp },
{ [P in FragmentKeysNoLocal<T>]: FragmentOrRegularProp<T[P]> }
> => (component: React.ComponentType<any>) => {
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;
};
@@ -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 <T>(
fragmentSpec: { [P in keyof T]: GraphQLTaggedNode },
fragmentSpec: { [P in FragmentKeysNoLocal<T>]: GraphQLTaggedNode } & {
_?: never;
},
refetchQuery: GraphQLTaggedNode
): InferableComponentEnhancerWithProps<
T & { relay: RelayRefetchProp },
{ [P in keyof T]: any }
> => (component: React.ComponentType<any>) =>
createRefetchContainer(component, fragmentSpec, refetchQuery) as any;
{ [P in FragmentKeysNoLocal<T>]: T[P] } & { relay: RelayRefetchProp },
{ [P in FragmentKeysNoLocal<T>]: FragmentOrRegularProp<T[P]> }
> => (component: React.ComponentType<any>) => {
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;
};
@@ -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;
}
@@ -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";
@@ -0,0 +1,16 @@
import { ComponentType } from "react";
/** Remove all traces of `$fragmentRefs` and `$refType` from type recursively */
export type NoFragmentRefs<T> = T extends object
? {
[P in Exclude<keyof T, " $fragmentRefs" | " $refType">]: NoFragmentRefs<
T[P]
>
}
: T;
export default function removeFragmentRefs<T>(
component: ComponentType<T>
): ComponentType<NoFragmentRefs<T>> {
return component as any;
}
+1 -1
View File
@@ -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 {
@@ -1,5 +1,6 @@
import { Localized } from "fluent-react/compat";
import React from "react";
import { oncePerFrame } from "talk-common/utils";
import {
Button,
@@ -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<typeof CommentContainer>["data"] | null;
showAllCommentsHref: string | null;
onShowAllComments: (e: MouseEvent<any>) => void;
}
@@ -27,13 +27,21 @@ export interface PostCommentFormProps {
const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
<Form onSubmit={props.onSubmit}>
{({ handleSubmit, submitting }) => (
<form autoComplete="off" onSubmit={handleSubmit} className={styles.root}>
<form
autoComplete="off"
onSubmit={handleSubmit}
className={styles.root}
id="comments-postCommentForm-form"
>
<HorizontalGutter>
<Field name="body" validate={required}>
{({ input, meta }) => (
<div>
<Localized id="comments-postCommentForm-rteLabel">
<AriaInfo component="label" htmlFor="postCommentField">
<AriaInfo
component="label"
htmlFor="comments-postCommentForm-field"
>
Post a comment
</AriaInfo>
</Localized>
@@ -42,7 +50,7 @@ const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
attrs={{ placeholder: true }}
>
<RTE
inputId="postCommentField"
inputId="comments-postCommentForm-field"
onChange={({ html }) => input.onChange(html)}
value={input.value}
placeholder="Post a comment"
@@ -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<typeof ReplyList> = {
const props: PropTypesOf<typeof ReplyListN> = {
commentID: "comment-id",
comments: [{ id: "comment-1" }, { id: "comment-2" }],
onShowAll: noop,
hasMore: false,
disableShowAll: false,
};
const wrapper = shallow(<ReplyList {...props} />);
const wrapper = shallow(<ReplyListN {...props} />);
expect(wrapper).toMatchSnapshot();
});
describe("when there is more", () => {
const props: PropTypesOf<typeof ReplyList> = {
const props: PropTypesOf<typeof ReplyListN> = {
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(<ReplyList {...props} />);
const wrapper = shallow(<ReplyListN {...props} />);
it("renders a load more button", () => {
expect(wrapper).toMatchSnapshot();
});
@@ -41,7 +44,7 @@ describe("when there is more", () => {
});
const wrapperDisabledButton = shallow(
<ReplyList {...props} disableShowAll />
<ReplyListN {...props} disableShowAll />
);
it("disables load more button", () => {
expect(wrapperDisabledButton).toMatchSnapshot();
@@ -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<typeof CommentContainer>["data"]
>;
onShowAll: () => void;
hasMore: boolean;
disableShowAll: boolean;
@@ -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<typeof Stream> = {
const props: PropTypesOf<typeof StreamN> = {
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(<Stream {...props} />);
const wrapper = shallow(<StreamN {...props} />);
expect(wrapper).toMatchSnapshot();
});
describe("when use is logged in", () => {
it("renders correctly", () => {
const props: PropTypesOf<typeof Stream> = {
const props: PropTypesOf<typeof StreamN> = {
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(<Stream {...props} />);
const wrapper = shallow(<StreamN {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
describe("when there is more", () => {
const props: PropTypesOf<typeof Stream> = {
const props: PropTypesOf<typeof StreamN> = {
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(<Stream {...props} />);
const wrapper = shallow(<StreamN {...props} />);
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(<Stream {...props} disableLoadMore />);
const wrapperDisabledButton = shallow(<StreamN {...props} disableLoadMore />);
it("disables load more button", () => {
expect(wrapperDisabledButton).toMatchSnapshot();
});
+6 -2
View File
@@ -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<typeof CommentContainer>["data"] &
PropTypesOf<typeof ReplyListContainer>["comment"]
>;
onLoadMore?: () => void;
hasMore?: boolean;
disableLoadMore?: boolean;
user: {} | null;
user: PropTypesOf<typeof UserBoxContainer>["user"] | null;
}
const Stream: StatelessComponent<StreamProps> = props => {
@@ -16,7 +16,7 @@ const AppContainer: StatelessComponent<InnerProps> = ({
return <App showPermalinkView={!!commentID} />;
};
const enhanced = withLocalStateContainer<Local>(
const enhanced = withLocalStateContainer(
graphql`
fragment AppContainerLocal on Local {
commentID
@@ -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<typeof CommentContainer> = {
const props: PropTypesOf<typeof CommentContainerN> = {
data: {
id: "comment-id",
author: {
@@ -17,12 +21,12 @@ it("renders username and body", () => {
},
};
const wrapper = shallow(<CommentContainer {...props} />);
const wrapper = shallow(<CommentContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders body only", () => {
const props: PropTypesOf<typeof CommentContainer> = {
const props: PropTypesOf<typeof CommentContainerN> = {
data: {
id: "comment-id",
author: {
@@ -33,6 +37,6 @@ it("renders body only", () => {
},
};
const wrapper = shallow(<CommentContainer {...props} />);
const wrapper = shallow(<CommentContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -28,7 +28,7 @@ export const CommentContainer: StatelessComponent<InnerProps> = props => {
return <Comment {...rest} {...props.data} />;
};
const enhanced = withFragmentContainer<{ data: Data }>({
const enhanced = withFragmentContainer<InnerProps>({
data: graphql`
fragment CommentContainer on Comment {
...CommentContainer_comment @relay(mask: false)
@@ -19,7 +19,7 @@ export const PermalinkContainer: StatelessComponent<InnerProps> = ({
) : null;
};
const enhanced = withLocalStateContainer<Local>(
const enhanced = withLocalStateContainer(
graphql`
fragment PermalinkButtonContainerLocal on Local {
assetURL
@@ -52,9 +52,7 @@ const enhanced = withContext(ctx => ({
pym: ctx.pym,
}))(
withSetCommentIDMutation(
withFragmentContainer<{
comment: CommentData | null;
}>({
withFragmentContainer<PermalinkViewContainerProps>({
comment: graphql`
fragment PermalinkViewContainer_comment on Comment {
...CommentContainer
@@ -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<InnerProps> {
@@ -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<typeof ReplyListContainer> = {
const props: PropTypesOf<typeof ReplyListContainerN> = {
comment: {
id: "comment-id",
replies: {
@@ -20,12 +24,12 @@ it("renders correctly", () => {
isLoading: noop,
} as any,
};
const wrapper = shallow(<ReplyListContainer {...props} />);
const wrapper = shallow(<ReplyListContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders correctly when replies are null", () => {
const props: PropTypesOf<typeof ReplyListContainer> = {
const props: PropTypesOf<typeof ReplyListContainerN> = {
comment: {
id: "comment-id",
replies: null,
@@ -35,13 +39,13 @@ it("renders correctly when replies are null", () => {
isLoading: noop,
} as any,
};
const wrapper = shallow(<ReplyListContainer {...props} />);
const wrapper = shallow(<ReplyListContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
});
describe("when has more replies", () => {
let finishLoading: ((error?: Error) => void) | null = null;
const props: PropTypesOf<ReplyListContainer> = {
const props: PropTypesOf<typeof ReplyListContainerN> = {
comment: {
id: "comment-id",
replies: {
@@ -57,7 +61,7 @@ describe("when has more replies", () => {
let wrapper: ShallowWrapper;
beforeAll(() => (wrapper = shallow(<ReplyListContainer {...props} />)));
beforeAll(() => (wrapper = shallow(<ReplyListContainerN {...props} />)));
it("renders hasMore", () => {
expect(wrapper).toMatchSnapshot();
@@ -23,7 +23,7 @@ export class ReplyListContainer extends React.Component<InnerProps> {
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`
@@ -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<StreamContainer> = {
const props: PropTypesOf<typeof StreamContainerN> = {
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(<StreamContainer {...props} />);
const wrapper = shallow(<StreamContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
});
describe("when has more comments", () => {
let finishLoading: ((error?: Error) => void) | null = null;
const props: PropTypesOf<StreamContainer> = {
const props: PropTypesOf<typeof StreamContainerN> = {
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(<StreamContainer {...props} />)));
beforeAll(() => (wrapper = shallow(<StreamContainerN {...props} />)));
it("renders hasMore", () => {
expect(wrapper).toMatchSnapshot();
@@ -64,10 +64,9 @@ interface FragmentVariables {
}
const enhanced = withPaginationContainer<
{ asset: AssetData; user: UserData | null },
InnerProps,
FragmentVariables,
StreamContainerPaginationQueryVariables
StreamContainerPaginationQueryVariables,
FragmentVariables
>(
{
asset: graphql`
@@ -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<UserBoxContainer> = {
const props: PropTypesOf<typeof UserBoxContainerN> = {
local: {
authPopup: {
open: false,
@@ -22,6 +26,6 @@ it("renders correctly", () => {
// tslint:disable-next-line:no-empty
signOut: async () => {},
};
const wrapper = shallow(<UserBoxContainer {...props} />);
const wrapper = shallow(<UserBoxContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -78,7 +78,7 @@ export class UserBoxContainer extends Component<InnerProps> {
const enhanced = withSignOutMutation(
withSetAuthPopupStateMutation(
withShowAuthPopupMutation(
withLocalStateContainer<Local>(
withLocalStateContainer(
graphql`
fragment UserBoxContainerLocal on Local {
authPopup {
@@ -89,7 +89,7 @@ const enhanced = withSignOutMutation(
}
`
)(
withFragmentContainer<{ user: UserData | null }>({
withFragmentContainer<InnerProps>({
user: graphql`
fragment UserBoxContainer_user on User {
username
@@ -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}
/>
`;
@@ -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<MutationTypes>(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<CreateCommentMutationResponse["createComment"]>;
) => Promise<MutationTypes["response"]["createComment"]>;
@@ -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<PermalinkViewQueryResponse>) => {
}: ReadyState<QueryTypes["response"]>) => {
if (error) {
return <div>{error.message}</div>;
}
@@ -36,7 +33,7 @@ export const render = ({
const PermalinkViewQuery: StatelessComponent<InnerProps> = ({
local: { commentID },
}) => (
<QueryRenderer<PermalinkViewQueryVariables, PermalinkViewQueryResponse>
<QueryRenderer<QueryTypes>
query={graphql`
query PermalinkViewQuery($commentID: ID!) {
comment(id: $commentID) {
@@ -45,13 +42,13 @@ const PermalinkViewQuery: StatelessComponent<InnerProps> = ({
}
`}
variables={{
commentID,
commentID: commentID!,
}}
render={render}
/>
);
const enhanced = withLocalStateContainer<Local>(
const enhanced = withLocalStateContainer(
graphql`
fragment PermalinkViewQueryLocal on Local {
commentID
+16 -8
View File
@@ -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<StreamQueryResponse>) => {
export const render = ({
error,
props,
}: ReadyState<QueryTypes["response"]>) => {
if (error) {
return <div>{error.message}</div>;
}
if (props) {
if (!props.asset) {
return (
<Localized id="comments-streamQuery-assetNotFound">
<div>Asset not found</div>
</Localized>
);
}
return <StreamContainer asset={props.asset} user={props.me} />;
}
@@ -32,7 +40,7 @@ export const render = ({ error, props }: ReadyState<StreamQueryResponse>) => {
const StreamQuery: StatelessComponent<InnerProps> = ({
local: { assetID, authRevision },
}) => (
<QueryRenderer<StreamQueryVariables, StreamQueryResponse>
<QueryRenderer<QueryTypes>
query={graphql`
query StreamQuery($assetID: ID!, $authRevision: Int!) {
asset(id: $assetID) {
@@ -47,14 +55,14 @@ const StreamQuery: StatelessComponent<InnerProps> = ({
}
`}
variables={{
assetID,
assetID: assetID!,
authRevision,
}}
render={render}
/>
);
const enhanced = withLocalStateContainer<Local>(
const enhanced = withLocalStateContainer(
graphql`
fragment StreamQueryLocal on Local {
assetID
@@ -0,0 +1,847 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`post a comment 1`] = `
<div
className="Flex-root App-root Flex-flex Flex-justifyCenter"
>
<div
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
>
<div
className="HorizontalGutter-root HorizontalGutter-half"
>
<div
className="Flex-root"
>
<div
className="Flex-flex Flex-halfItemGutter Flex-wrap"
>
<div
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Signed in as
<span
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
>
Markus
</span>
.
</div>
<div
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
>
<span>
Not you? 
</span>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Sign Out
</button>
</div>
</div>
</div>
<form
autoComplete="off"
className="PostCommentForm-root"
id="comments-postCommentForm-form"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div>
<label
className="AriaInfo-root"
htmlFor="comments-postCommentForm-field"
>
Post a comment
</label>
<div>
<div
className=""
>
<div
className="Toolbar-toolbar"
>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Bold"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
format_bold
</span>
</button>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Italic"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
format_italic
</span>
</button>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Blockquote"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
format_quote
</span>
</button>
</div>
<div
aria-placeholder="Post a comment"
className="RTE-contentEditable RTE-content"
contentEditable={true}
dangerouslySetInnerHTML={
Object {
"__html": "<strong>Hello world!</strong>",
}
}
id="comments-postCommentForm-field"
onBlur={[Function]}
onChange={[Function]}
onCut={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onPaste={[Function]}
onSelect={[Function]}
/>
</div>
</div>
</div>
<div
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-alignFlexStart Flex-directionRow"
>
<div
className="PostCommentForm-poweredBy"
>
<span
className="Typography-root Typography-inputDescription Typography-colorTextSecondary"
>
Powered by
<span
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
>
The Coral Project
</span>
</span>
</div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-disabled"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Submit
</button>
</div>
</div>
</form>
</div>
<div
aria-live="polite"
className="HorizontalGutter-root HorizontalGutter-full"
id="talk-comments-stream-log"
role="log"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root TopBar-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.002Z"
title="2018-07-06T18:24:00.002Z"
>
2018-07-06T18:24:00.002Z
</time>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "<strong>Hello world!</strong>",
}
}
/>
<div
className="Comment-footer"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root TopBar-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Comment-footer"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root TopBar-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
/>
<div
className="Comment-footer"
/>
</div>
</div>
</div>
</div>
</div>
`;
exports[`post a comment 2`] = `
<div
className="Flex-root App-root Flex-flex Flex-justifyCenter"
>
<div
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
>
<div
className="HorizontalGutter-root HorizontalGutter-half"
>
<div
className="Flex-root"
>
<div
className="Flex-flex Flex-halfItemGutter Flex-wrap"
>
<div
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Signed in as
<span
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
>
Markus
</span>
.
</div>
<div
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
>
<span>
Not you? 
</span>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Sign Out
</button>
</div>
</div>
</div>
<form
autoComplete="off"
className="PostCommentForm-root"
id="comments-postCommentForm-form"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div>
<label
className="AriaInfo-root"
htmlFor="comments-postCommentForm-field"
>
Post a comment
</label>
<div>
<div
className=""
>
<div
className="Toolbar-toolbar"
>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Bold"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
format_bold
</span>
</button>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Italic"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
format_italic
</span>
</button>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Blockquote"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
format_quote
</span>
</button>
</div>
<div
aria-hidden="true"
className="RTE-placeholder RTE-placeholder"
>
Post a comment
</div>
<div
aria-placeholder="Post a comment"
className="RTE-contentEditable RTE-content"
contentEditable={true}
dangerouslySetInnerHTML={
Object {
"__html": "",
}
}
id="comments-postCommentForm-field"
onBlur={[Function]}
onChange={[Function]}
onCut={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onPaste={[Function]}
onSelect={[Function]}
/>
</div>
</div>
</div>
<div
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-alignFlexStart Flex-directionRow"
>
<div
className="PostCommentForm-poweredBy"
>
<span
className="Typography-root Typography-inputDescription Typography-colorTextSecondary"
>
Powered by
<span
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
>
The Coral Project
</span>
</span>
</div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Submit
</button>
</div>
</div>
</form>
</div>
<div
aria-live="polite"
className="HorizontalGutter-root HorizontalGutter-full"
id="talk-comments-stream-log"
role="log"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root TopBar-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "<strong>Hello world! (from server)</strong>",
}
}
/>
<div
className="Comment-footer"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root TopBar-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Comment-footer"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root TopBar-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
/>
<div
className="Comment-footer"
/>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders comment stream 1`] = `
<div
className="Flex-root App-root Flex-flex Flex-justifyCenter"
>
<div
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
>
<div
className="HorizontalGutter-root HorizontalGutter-half"
>
<div
className="Flex-root"
>
<div
className="Flex-flex Flex-halfItemGutter Flex-wrap"
>
<div
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Signed in as
<span
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
>
Markus
</span>
.
</div>
<div
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
>
<span>
Not you? 
</span>
<button
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Sign Out
</button>
</div>
</div>
</div>
<form
autoComplete="off"
className="PostCommentForm-root"
id="comments-postCommentForm-form"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div>
<label
className="AriaInfo-root"
htmlFor="comments-postCommentForm-field"
>
Post a comment
</label>
<div>
<div
className=""
>
<div
className="Toolbar-toolbar"
>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Bold"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
format_bold
</span>
</button>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Italic"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
format_italic
</span>
</button>
<button
className="Button-button"
disabled={false}
onClick={[Function]}
title="Blockquote"
type="button"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
format_quote
</span>
</button>
</div>
<div
aria-hidden="true"
className="RTE-placeholder RTE-placeholder"
>
Post a comment
</div>
<div
aria-placeholder="Post a comment"
className="RTE-contentEditable RTE-content"
contentEditable={true}
dangerouslySetInnerHTML={
Object {
"__html": "",
}
}
id="comments-postCommentForm-field"
onBlur={[Function]}
onChange={[Function]}
onCut={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onPaste={[Function]}
onSelect={[Function]}
/>
</div>
</div>
</div>
<div
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-alignFlexStart Flex-directionRow"
>
<div
className="PostCommentForm-poweredBy"
>
<span
className="Typography-root Typography-inputDescription Typography-colorTextSecondary"
>
Powered by
<span
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
>
The Coral Project
</span>
</span>
</div>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Submit
</button>
</div>
</div>
</form>
</div>
<div
aria-live="polite"
className="HorizontalGutter-root HorizontalGutter-full"
id="talk-comments-stream-log"
role="log"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root TopBar-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Markus
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:24:00.000Z"
title="2018-07-06T18:24:00.000Z"
>
2018-07-06T18:24:00.000Z
</time>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "Joining Too",
}
}
/>
<div
className="Comment-footer"
/>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Comment-root"
role="article"
>
<div
className="Flex-root TopBar-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
>
Lukas
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
<div
className="HTMLContent-root"
dangerouslySetInnerHTML={
Object {
"__html": "What's up?",
}
}
/>
<div
className="Comment-footer"
/>
</div>
</div>
</div>
</div>
</div>
`;
+44 -43
View File
@@ -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)
),
},
};
@@ -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)
),
},
};
@@ -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)
),
},
};
@@ -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: "<strong>Hello world!</strong>",
clientMutationId: "0",
},
})
.returns({
commentEdge: {
cursor: "2018-07-06T18:24:00.000Z",
node: {
id: "comment-x",
author: users[0],
body: "<strong>Hello world! (from server)</strong>",
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(
<TalkContextProvider value={context}>
<AppContainer />
</TalkContextProvider>,
{ 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: "<strong>Hello world!</strong>" });
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();
});
@@ -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)
),
},
};
@@ -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])
),
},
};
@@ -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)
),
},
};
+9
View File
@@ -5,6 +5,15 @@ declare var global: any;
const jsdom = new JSDOM("<!doctype html><html><body></body></html>");
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")
@@ -1,10 +1,17 @@
import { GQLMutationTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
const Mutation: GQLMutationTypeResolver<void> = {
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,
@@ -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.
+2
View File
@@ -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