diff --git a/config/jest.config.js b/config/jest.config.js index ed9b641ae..d2f436f94 100644 --- a/config/jest.config.js +++ b/config/jest.config.js @@ -1,3 +1,4 @@ +const path = require("path"); const paths = require("./paths"); module.exports = { @@ -5,11 +6,8 @@ module.exports = { roots: ["/src", "/scripts"], collectCoverageFrom: ["src/**/*.{js,jsx,mjs,ts,tsx}"], coveragePathIgnorePatterns: ["/node_modules/"], - setupFiles: ["/config/polyfills.js"], - testMatch: [ - "**/__tests__/**/*.{js,jsx,mjs,ts,tsx}", - "**/*.spec.{js,jsx,mjs,ts,tsx}", - ], + setupFiles: ["/config/polyfills.js", "/test/setupjest.ts"], + testMatch: ["**/*.spec.{js,jsx,mjs,ts,tsx}"], testEnvironment: "node", testURL: "http://localhost", transform: { @@ -40,4 +38,11 @@ module.exports = { "ts", "tsx", ], + snapshotSerializers: ["enzyme-to-json/serializer"], + globals: { + "ts-jest": { + useBabelrc: true, + tsConfigFile: path.resolve(__dirname, "tsconfig.jest.json"), + }, + }, }; diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js index 90b6bfa63..98d4e9a36 100644 --- a/config/jest/cssTransform.js +++ b/config/jest/cssTransform.js @@ -4,7 +4,7 @@ // Copyright https://github.com/Connormiha/jest-css-modules-transform/graphs/contributors // This oututs `module.exports = cssObject` instead of `module.exports = { default: cssObject }`; -const { sep: pathSep, resolve } = require("path"); +const { sep: pathSep, resolve, basename, extname } = require("path"); const postcss = require("postcss"); const postcssNested = postcss([require("postcss-nested")]); @@ -88,7 +88,18 @@ module.exports = { process(src, path, config) { const filename = path.slice(path.lastIndexOf(pathSep) + 1); const textCSS = postcssNested.process(src).css; - const exprt = JSON.stringify(getCSSSelectors(textCSS, path)); + const selectors = getCSSSelectors(textCSS, path); + const component = basename(path, extname(path)); + + // Prefix class names with component name. + Object.keys(selectors).forEach(k => { + selectors[k] = selectors[k] + .split(/\s+/) + .map(v => `${component}-${v}`) + .join(" "); + }); + + const exprt = JSON.stringify(selectors); return `module.exports = ${exprt}`; }, }; diff --git a/config/tsconfig.jest.json b/config/tsconfig.jest.json new file mode 100644 index 000000000..d457255e7 --- /dev/null +++ b/config/tsconfig.jest.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "target": "es2015", + "module": "es2015" + } +} diff --git a/config/watcher.ts b/config/watcher.ts index 0d3bf23c1..89da62bf6 100644 --- a/config/watcher.ts +++ b/config/watcher.ts @@ -13,9 +13,14 @@ const config: Config = { "core/client/stream/**/*.ts", "core/client/stream/**/*.tsx", "core/client/stream/**/*.graphql", - "core/client/server/**/*.graphql", + "core/server/**/*.graphql", + ], + ignore: [ + "core/**/*.d.ts", + "core/**/*.graphql.ts", + "**/test/**/*", + "core/**/*.spec.*", ], - ignore: ["core/**/*.d.ts", "core/**/*.graphql.ts"], executor: new CommandExecutor("npm run compile:relay-stream", { runOnInit: true, }), diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 80872c5a4..fcbb75fe2 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -224,6 +224,11 @@ module.exports = { jsx: "preserve", noEmit: false, }, + + // Overwrites the behavior of `include` and `exclude` to only + // include files that are actually being imported and which + // are necessary to compile the bundle. + onlyCompileBundledFiles: true, }, }, ], diff --git a/package-lock.json b/package-lock.json index c6dd91c93..7f7c9169e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -773,14 +773,115 @@ } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.49.tgz", - "integrity": "sha1-Cfs0XVknwro72J582xOlUGftOaA=", + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.52.tgz", + "integrity": "sha1-AQTvGDzcL9Q9CGAhHMzOee8YAX4=", "dev": true, "requires": { - "@babel/helper-module-transforms": "7.0.0-beta.49", - "@babel/helper-plugin-utils": "7.0.0-beta.49", - "@babel/helper-simple-access": "7.0.0-beta.49" + "@babel/helper-module-transforms": "7.0.0-beta.52", + "@babel/helper-plugin-utils": "7.0.0-beta.52", + "@babel/helper-simple-access": "7.0.0-beta.52" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.52.tgz", + "integrity": "sha1-GSSDv6DR5GfBAVccIQKcy3SvKAE=", + "dev": true, + "requires": { + "@babel/highlight": "7.0.0-beta.52" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.52.tgz", + "integrity": "sha1-cIQOg66JH5RwLGxhN4fEjuPJZbs=", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.52", + "lodash": "^4.17.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.52.tgz", + "integrity": "sha1-vIRE6tJSo3LJKJlq4XM96vOwjJA=", + "dev": true, + "requires": { + "@babel/helper-module-imports": "7.0.0-beta.52", + "@babel/helper-simple-access": "7.0.0-beta.52", + "@babel/helper-split-export-declaration": "7.0.0-beta.52", + "@babel/template": "7.0.0-beta.52", + "@babel/types": "7.0.0-beta.52", + "lodash": "^4.17.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-beta.52.tgz", + "integrity": "sha1-LwWMX3w6X+S8IZA2sueOEb3et60=", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.52.tgz", + "integrity": "sha1-0plc6cTJ8D/nKvkiNzZ3qOtkJO4=", + "dev": true, + "requires": { + "@babel/template": "7.0.0-beta.52", + "@babel/types": "7.0.0-beta.52", + "lodash": "^4.17.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.52.tgz", + "integrity": "sha1-SqxPMOpjhK82duBLUkZydjLkYN8=", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.52" + } + }, + "@babel/highlight": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.52.tgz", + "integrity": "sha1-7ySTFDLwYVXnvDnNuKaze0oos9A=", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" + } + }, + "@babel/parser": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.52.tgz", + "integrity": "sha1-TpNbYs2b+HK9N7zx9j2C/nsCN6I=", + "dev": true + }, + "@babel/template": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.52.tgz", + "integrity": "sha1-ROGPrDglH1f5JRHWdI8JWrAvmW4=", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-beta.52", + "@babel/parser": "7.0.0-beta.52", + "@babel/types": "7.0.0-beta.52", + "lodash": "^4.17.5" + } + }, + "@babel/types": { + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.52.tgz", + "integrity": "sha1-o+ViCxU0slOlCrzyIitSDiOxbaI=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/plugin-transform-modules-systemjs": { @@ -1090,6 +1191,19 @@ "browserslist": "^3.0.0", "invariant": "^2.2.2", "semver": "^5.3.0" + }, + "dependencies": { + "@babel/plugin-transform-modules-commonjs": { + "version": "7.0.0-beta.49", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.49.tgz", + "integrity": "sha1-Cfs0XVknwro72J582xOlUGftOaA=", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "7.0.0-beta.49", + "@babel/helper-plugin-utils": "7.0.0-beta.49", + "@babel/helper-simple-access": "7.0.0-beta.49" + } + } } }, "@babel/preset-flow": { @@ -1403,6 +1517,15 @@ "lodash.deburr": "^4.1.0" } }, + "@sinonjs/formatio": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "dev": true, + "requires": { + "samsam": "1.3.0" + } + }, "@types/bluebird": { "version": "3.5.20", "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.20.tgz", @@ -1438,6 +1561,12 @@ "@types/node": "*" } }, + "@types/cheerio": { + "version": "0.22.8", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.8.tgz", + "integrity": "sha512-LzF540VOFabhS2TR2yYFz2Mu/fTfkA+5AwYddtJbOJGwnYrr2e7fHadT7/Z3jNGJJdCRlO3ySxmW26NgRdwhNA==", + "dev": true + }, "@types/chokidar": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@types/chokidar/-/chokidar-1.7.5.tgz", @@ -1496,6 +1625,25 @@ "@types/node": "*" } }, + "@types/enzyme": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.1.11.tgz", + "integrity": "sha512-abPTpLuveNVd2ibafCjwoZT9MerzgnBKd6ijNKdtlfJREGKXk5dxzKAXGoY9IuiYarH2YXTc197WeyIVQUFjQg==", + "dev": true, + "requires": { + "@types/cheerio": "*", + "@types/react": "*" + } + }, + "@types/enzyme-adapter-react-16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.2.tgz", + "integrity": "sha512-/oEtlwJyFIT9metXC2A90XnjfHwBDYxhFoJwqNjNDG5K2CCqp7xneQbAp4u5j280bOmalFYUDjfmmxNQG3S4Og==", + "dev": true, + "requires": { + "@types/enzyme": "*" + } + }, "@types/events": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", @@ -1552,6 +1700,18 @@ "integrity": "sha512-nOnsbHvoo5DsQEh8VGlbQlfg9+/iFSxE5RQKLNkAODIqyupdEkBCZf6RCNxR+9X0egMIkJ43NnwkEJKxLogsIA==", "dev": true }, + "@types/jsdom": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-11.0.6.tgz", + "integrity": "sha512-6sw9iNdXak3Dk4iN1osdZeZcV8cTe2SNIVwbnt8MjFFAI8+nBWDNcDfjUgZZqycF3Twlr7xXlKntnOrruxNRvg==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^4.0.0" + } + }, "@types/lodash": { "version": "4.14.109", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.109.tgz", @@ -1665,6 +1825,18 @@ "@types/mime": "*" } }, + "@types/sinon": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-5.0.1.tgz", + "integrity": "sha512-yxzBCIjE3lp9lYjfBbIK/LRCoXgCLLbIIBIje7eNCcUIIR2CZZtyX5uto2hVoMSMqLrsRrT6mwwUEd0yFgOwpA==", + "dev": true + }, + "@types/tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ==", + "dev": true + }, "@types/uuid": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.3.tgz", @@ -2528,45 +2700,10 @@ } }, "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - }, - "dependencies": { - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - } - } + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "dev": true }, "babel-generator": { "version": "6.26.1", @@ -4151,6 +4288,45 @@ "source-map-support": "^0.4.15" }, "dependencies": { + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, "source-map-support": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", @@ -4334,9 +4510,9 @@ "dev": true }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "optional": true, "requires": { @@ -4974,6 +5150,54 @@ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + }, + "dependencies": { + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "*" + } + } + } + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -6231,9 +6455,9 @@ } }, "cssom": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", - "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", + "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", "dev": true }, "cssstyle": { @@ -6615,6 +6839,12 @@ "is-woff2": "^1.0.0" } }, + "discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", + "dev": true + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -7264,6 +7494,65 @@ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", "dev": true }, + "enzyme": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.3.0.tgz", + "integrity": "sha512-l8csyPyLmtxskTz6pX9W8eDOyH1ckEtDttXk/vlFWCjv00SkjTjtoUrogqp4yEvMyneU9dUJoOLnqFoiHb8IHA==", + "dev": true, + "requires": { + "cheerio": "^1.0.0-rc.2", + "function.prototype.name": "^1.0.3", + "has": "^1.0.1", + "is-boolean-object": "^1.0.0", + "is-callable": "^1.1.3", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-subset": "^0.1.1", + "lodash": "^4.17.4", + "object-inspect": "^1.5.0", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4", + "object.values": "^1.0.4", + "raf": "^3.4.0", + "rst-selector-parser": "^2.2.3" + } + }, + "enzyme-adapter-react-16": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz", + "integrity": "sha512-kC8pAtU2Jk3OJ0EG8Y2813dg9Ol0TXi7UNxHzHiWs30Jo/hj7alc//G1YpKUsPP1oKl9X+Lkx+WlGJpPYA+nvw==", + "dev": true, + "requires": { + "enzyme-adapter-utils": "^1.3.0", + "lodash": "^4.17.4", + "object.assign": "^4.0.4", + "object.values": "^1.0.4", + "prop-types": "^15.6.0", + "react-reconciler": "^0.7.0", + "react-test-renderer": "^16.0.0-0" + } + }, + "enzyme-adapter-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz", + "integrity": "sha512-vVXSt6uDv230DIv+ebCG66T1Pm36Kv+m74L1TrF4kaE7e1V7Q/LcxO0QRkajk5cA6R3uu9wJf5h13wOTezTbjA==", + "dev": true, + "requires": { + "lodash": "^4.17.4", + "object.assign": "^4.0.4", + "prop-types": "^15.6.0" + } + }, + "enzyme-to-json": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.3.4.tgz", + "integrity": "sha1-Z8YEDpMRgvGDQYry659DIyWKp38=", + "dev": true, + "requires": { + "lodash": "^4.17.4" + } + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -8821,6 +9110,17 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "function.prototype.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.0.tgz", + "integrity": "sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "is-callable": "^1.1.3" + } + }, "get-caller-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", @@ -10226,6 +10526,12 @@ "binary-extensions": "^1.0.0" } }, + "is-boolean-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", + "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", + "dev": true + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -10430,6 +10736,12 @@ } } }, + "is-number-object": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz", + "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", + "dev": true + }, "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", @@ -10552,6 +10864,18 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-string": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz", + "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", + "dev": true + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, "is-svg": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", @@ -10962,6 +11286,39 @@ "pretty-format": "^23.2.0" }, "dependencies": { + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -10975,6 +11332,12 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true } } }, @@ -11203,6 +11566,47 @@ "strip-bom": "3.0.0", "write-file-atomic": "^2.1.0", "yargs": "^11.0.0" + }, + "dependencies": { + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + } } }, "jest-serializer": { @@ -11440,6 +11844,12 @@ "verror": "1.10.0" } }, + "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==", + "dev": true + }, "keygrip": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.2.tgz", @@ -11888,6 +12298,12 @@ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, "lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", @@ -12028,6 +12444,12 @@ "object.assign": "^4.1.0" } }, + "lolex": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.1.tgz", + "integrity": "sha512-Oo2Si3RMKV3+lV5MsSWplDQFoTClz/24S0MMHYcgGWWmFXr6TMlqcqk/l1GtH+d5wLBwNRiqGnwDRMirtFalJw==", + "dev": true + }, "long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", @@ -12678,6 +13100,18 @@ "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, + "nearley": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.13.0.tgz", + "integrity": "sha512-ioYYogSaZhFlCpRizQgY3UT3G1qFXmHGY/5ozoFE3dMfiCRAeJfh+IPE3/eh9gCZvqLhPCWb4bLt7Bqzo+1mLQ==", + "dev": true, + "requires": { + "nomnom": "~1.6.2", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6", + "semver": "^5.4.1" + } + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -12701,6 +13135,36 @@ "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", "dev": true }, + "nise": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.2.tgz", + "integrity": "sha512-BxH/DxoQYYdhKgVAfqVy4pzXRZELHOIewzoesxpjYvpU+7YOalQhGNPf7wAx8pLrTNPrHRDlLOkAl8UI0ZpXjw==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^2.0.0", + "just-extend": "^1.1.27", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0", + "text-encoding": "^0.6.4" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", @@ -12793,6 +13257,24 @@ "which": "^1.3.0" } }, + "nomnom": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", + "integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=", + "dev": true, + "requires": { + "colors": "0.5.x", + "underscore": "~1.4.4" + }, + "dependencies": { + "colors": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=", + "dev": true + } + } + }, "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", @@ -12973,6 +13455,18 @@ } } }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "dev": true + }, "object-keys": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", @@ -13000,6 +13494,18 @@ "object-keys": "^1.0.11" } }, + "object.entries": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", + "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.6.1", + "function-bind": "^1.1.0", + "has": "^1.0.1" + } + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -13029,6 +13535,18 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz", + "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.6.1", + "function-bind": "^1.1.0", + "has": "^1.0.1" + } + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -15987,6 +16505,31 @@ "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", "dev": true }, + "raf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", + "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", + "dev": true, + "requires": { + "performance-now": "^2.1.0" + } + }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", + "dev": true + }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, "randomatic": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", @@ -16393,6 +16936,18 @@ "integrity": "sha512-2bBoBaulpNnmXvM4nTQL3KKdsyzcufHI9ebpzoWlFZAplM0gn7Z0cBOcw2H8FFHGI38OMzo8CSpKYJaOJ8e/KA==", "dev": true }, + "react-reconciler": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.7.0.tgz", + "integrity": "sha512-50JwZ3yNyMS8fchN+jjWEJOH3Oze7UmhxeoJLn2j6f3NjpfCRbcmih83XTWmzqtar/ivd5f7tvQhvvhism2fgg==", + "dev": true, + "requires": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + } + }, "react-relay": { "version": "github:coralproject/patched#aad037f8cbcd9c14d5f6fd96cfafb24c39eab937", "from": "github:coralproject/patched#react-relay", @@ -17003,6 +17558,12 @@ "invariant": "^2.2.2" } }, + "relay-local-schema": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/relay-local-schema/-/relay-local-schema-0.7.0.tgz", + "integrity": "sha1-onxxcMYUi5Fu0PhNbDAAm6MyoOQ=", + "dev": true + }, "relay-runtime": { "version": "github:coralproject/patched#9dda7718393cb3aa6647bd3badf574c025f79d12", "from": "github:coralproject/patched#relay-runtime", @@ -17437,6 +17998,16 @@ "inherits": "^2.0.1" } }, + "rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", + "dev": true, + "requires": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, "rsvp": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", @@ -17519,6 +18090,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "dev": true + }, "sane": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", @@ -17781,6 +18358,32 @@ } } }, + "sinon": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.1.2.tgz", + "integrity": "sha512-MPa7kfV44XgzlXthSI28QgJUAjNQ2LdafqYFQnwXv1d5bCJXrVyYH7PLFk8PpAFhRN5Tgy8PbJFJbrvDsTss8g==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^2.0.0", + "diff": "^3.5.0", + "lodash.get": "^4.4.2", + "lolex": "^2.4.2", + "nise": "^1.3.3", + "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==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "sisteransi": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", @@ -18576,6 +19179,12 @@ } } }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -18833,357 +19442,110 @@ "dev": true }, "ts-jest": { - "version": "22.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-22.4.6.tgz", - "integrity": "sha512-kYQ6g1G1AU+bOO9rv+SSQXg4WTcni6Wx3AM48iHni0nP1vIuhdNRjKTE9Cxx36Ix/IOV7L85iKu07dgXJzH2pQ==", + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.0.0.tgz", + "integrity": "sha512-drIjGaoIfXgNi0UUxu963M57kgf+NE2xw386UmD/Ai2hmVpKPCcF2xRwnNMobnVfLQw4v6HSblTKuBg6v+pEBQ==", "dev": true, "requires": { - "babel-core": "^6.26.3", "babel-plugin-istanbul": "^4.1.6", - "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", - "babel-preset-jest": "^22.4.3", + "babel-preset-jest": "^23.0.0", "cpx": "^1.5.0", - "fs-extra": "6.0.0", - "jest-config": "^22.4.3", + "fs-extra": "6.0.1", + "jest-config": "^23.0.0", "lodash": "^4.17.10", - "pkg-dir": "^2.0.0", - "source-map-support": "^0.5.5", - "yargs": "^11.0.0" + "pkg-dir": "^3.0.0", + "yargs": "^12.0.1" }, "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 - }, - "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" - } - }, - "arr-diff": { + "decamelize": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "xregexp": "4.0.0" } }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", "dev": true }, - "babel-plugin-jest-hoist": { - "version": "22.4.4", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.4.tgz", - "integrity": "sha512-DUvGfYaAIlkdnygVIEl0O4Av69NtuQWcrjMOv6DODPuhuGLDnbsARz3AwiiI/EkIMMlxQDUcrZ9yoyJvTNjcVQ==", - "dev": true - }, - "babel-preset-jest": { - "version": "22.4.4", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-22.4.4.tgz", - "integrity": "sha512-+dxMtOFwnSYWfum0NaEc0O03oSdwBsjx4tMSChRDPGwu/4wSY6Q6ANW3wkjKpJzzguaovRs/DODcT4hbSN8yiA==", + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^22.4.4", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" + "find-up": "^3.0.0" } }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "yargs": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz", + "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" } }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "camelcase": "^4.1.0" } - }, - "expect": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-22.4.3.tgz", - "integrity": "sha512-XcNXEPehqn8b/jm8FYotdX0YrXn36qp4HWlrVT4ktwQas1l1LPxiVWncYnnL2eyMtKAmVIaG0XAp0QlrqJaxaA==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "jest-diff": "^22.4.3", - "jest-get-type": "^22.4.3", - "jest-matcher-utils": "^22.4.3", - "jest-message-util": "^22.4.3", - "jest-regex-util": "^22.4.3" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "fs-extra": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.0.tgz", - "integrity": "sha512-lk2cUCo8QzbiEWEbt7Cw3m27WMiRG321xsssbcIpfMhpRjrlC08WBOVQqj1/nQYYNnPtyIhP1oqLO3QwT2tPCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "jest-config": { - "version": "22.4.4", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-22.4.4.tgz", - "integrity": "sha512-9CKfo1GC4zrXSoMLcNeDvQBfgtqGTB1uP8iDIZ97oB26RCUb886KkKWhVcpyxVDOUxbhN+uzcBCeFe7w+Iem4A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^22.4.1", - "jest-environment-node": "^22.4.1", - "jest-get-type": "^22.1.0", - "jest-jasmine2": "^22.4.4", - "jest-regex-util": "^22.1.0", - "jest-resolve": "^22.4.2", - "jest-util": "^22.4.1", - "jest-validate": "^22.4.4", - "pretty-format": "^22.4.0" - } - }, - "jest-diff": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-22.4.3.tgz", - "integrity": "sha512-/QqGvCDP5oZOF6PebDuLwrB2BMD8ffJv6TAGAdEVuDx1+uEgrHpSFrfrOiMRx2eJ1hgNjlQrOQEHetVwij90KA==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.4.3", - "pretty-format": "^22.4.3" - } - }, - "jest-environment-jsdom": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", - "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", - "dev": true, - "requires": { - "jest-mock": "^22.4.3", - "jest-util": "^22.4.3", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-22.4.3.tgz", - "integrity": "sha512-reZl8XF6t/lMEuPWwo9OLfttyC26A5AMgDyEQ6DBgZuyfyeNUzYT8BFo6uxCCP/Av/b7eb9fTi3sIHFPBzmlRA==", - "dev": true, - "requires": { - "jest-mock": "^22.4.3", - "jest-util": "^22.4.3" - } - }, - "jest-jasmine2": { - "version": "22.4.4", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-22.4.4.tgz", - "integrity": "sha512-nK3vdUl50MuH7vj/8at7EQVjPGWCi3d5+6aCi7Gxy/XMWdOdbH1qtO/LjKbqD8+8dUAEH+BVVh7HkjpCWC1CSw==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^22.4.0", - "graceful-fs": "^4.1.11", - "is-generator-fn": "^1.0.0", - "jest-diff": "^22.4.0", - "jest-matcher-utils": "^22.4.0", - "jest-message-util": "^22.4.0", - "jest-snapshot": "^22.4.0", - "jest-util": "^22.4.1", - "source-map-support": "^0.5.0" - } - }, - "jest-matcher-utils": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz", - "integrity": "sha512-lsEHVaTnKzdAPR5t4B6OcxXo9Vy4K+kRRbG5gtddY8lBEC+Mlpvm1CJcsMESRjzUhzkz568exMV1hTB76nAKbA==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.4.3", - "pretty-format": "^22.4.3" - } - }, - "jest-message-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", - "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", - "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==", - "dev": true - }, - "jest-regex-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-22.4.3.tgz", - "integrity": "sha512-LFg1gWr3QinIjb8j833bq7jtQopiwdAs67OGfkPrvy7uNUbVMfTXXcOKXJaeY5GgjobELkKvKENqq1xrUectWg==", - "dev": true - }, - "jest-resolve": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-22.4.3.tgz", - "integrity": "sha512-u3BkD/MQBmwrOJDzDIaxpyqTxYH+XqAXzVJP51gt29H8jpj3QgKof5GGO2uPGKGeA1yTMlpbMs1gIQ6U4vcRhw==", - "dev": true, - "requires": { - "browser-resolve": "^1.11.2", - "chalk": "^2.0.1" - } - }, - "jest-snapshot": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-22.4.3.tgz", - "integrity": "sha512-JXA0gVs5YL0HtLDCGa9YxcmmV2LZbwJ+0MfyXBBc5qpgkEYITQFJP7XNhcHFbUvRiniRpRbGVfJrOoYhhGE0RQ==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-diff": "^22.4.3", - "jest-matcher-utils": "^22.4.3", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^22.4.3" - } - }, - "jest-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", - "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", - "dev": true, - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^22.4.3", - "mkdirp": "^0.5.1", - "source-map": "^0.6.0" - } - }, - "jest-validate": { - "version": "22.4.4", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-22.4.4.tgz", - "integrity": "sha512-dmlf4CIZRGvkaVg3fa0uetepcua44DHtktHm6rcoNVtYlpwe6fEJRkMFsaUVcFHLzbuBJ2cPw9Gl9TKfnzMVwg==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-config": "^22.4.4", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^22.4.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "pretty-format": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.3.tgz", - "integrity": "sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -19358,6 +19720,12 @@ "prelude-ls": "~1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -19643,6 +20011,12 @@ "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", "dev": true }, + "underscore": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=", + "dev": true + }, "unherit": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.1.tgz", @@ -20955,6 +21329,12 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 820605d35..944a521f6 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "devDependencies": { "@babel/core": "7.0.0-beta.49", "@babel/plugin-syntax-dynamic-import": "7.0.0-beta.49", + "@babel/plugin-transform-modules-commonjs": "^7.0.0-beta.52", "@babel/polyfill": "7.0.0-beta.49", "@babel/preset-env": "7.0.0-beta.49", "@babel/preset-react": "7.0.0-beta.49", @@ -61,12 +62,15 @@ "@types/convict": "^4.2.0", "@types/cross-spawn": "^6.0.0", "@types/dotenv": "^4.0.3", + "@types/enzyme": "^3.1.11", + "@types/enzyme-adapter-react-16": "^1.0.2", "@types/express": "^4.16.0", "@types/graphql": "^0.13.1", "@types/ioredis": "^3.2.8", "@types/jest": "^23.1.1", "@types/joi": "^13.0.8", "@types/lodash": "^4.14.109", + "@types/jsdom": "^11.0.6", "@types/luxon": "^0.5.3", "@types/mongodb": "^3.0.19", "@types/node": "^10.3.1", @@ -77,9 +81,11 @@ "@types/react-test-renderer": "^16.0.1", "@types/recompose": "^0.26.1", "@types/relay-runtime": "github:coralproject/patched#types/relay-runtime", + "@types/sinon": "^5.0.1", "@types/uuid": "^3.4.3", "@types/ws": "^5.1.2", "autoprefixer": "^8.6.0", + "babel-core": "^7.0.0-bridge.0", "babel-loader": "^8.0.0-beta", "babel-plugin-relay": "github:coralproject/patched#babel-plugin-relay", "babel-preset-react-optimize": "^1.0.1", @@ -93,6 +99,9 @@ "css-loader": "^0.28.11", "docz": "^0.2.6", "docz-theme-default": "^0.2.10", + "enzyme": "^3.3.0", + "enzyme-adapter-react-16": "^1.1.1", + "enzyme-to-json": "^3.3.4", "extract-text-webpack-plugin": "^4.0.0-beta.0", "final-form": "^4.8.1", "flat": "^4.0.0", @@ -103,6 +112,7 @@ "graphql-playground-middleware-express": "^1.7.0", "html-webpack-plugin": "^3.2.0", "jest": "^23.3.0", + "jsdom": "^11.11.0", "loader-utils": "^1.1.0", "npm-run-all": "^4.1.3", "postcss-flexbugs-fixes": "^3.3.1", @@ -121,10 +131,12 @@ "recompose": "^0.27.1", "relay-compiler": "github:coralproject/patched#relay-compiler", "relay-compiler-language-typescript": "github:coralproject/patched#relay-compiler-language-typescript", + "relay-local-schema": "^0.7.0", "relay-runtime": "github:coralproject/patched#relay-runtime", "relay-test-utils": "github:coralproject/patched#relay-test-utils", + "sinon": "^6.1.2", "style-loader": "^0.21.0", - "ts-jest": "^22.4.6", + "ts-jest": "^23.0.0", "ts-loader": "^4.3.1", "ts-node": "^6.1.0", "tsconfig-paths": "^3.4.0", diff --git a/src/core/client/.babelrc.js b/src/core/client/.babelrc.js index 6650ad9c9..02fdffd71 100644 --- a/src/core/client/.babelrc.js +++ b/src/core/client/.babelrc.js @@ -8,5 +8,8 @@ module.exports = { production: { plugins: [], }, + test: { + plugins: ["@babel/transform-modules-commonjs"], + }, }, }; diff --git a/src/core/client/framework/lib/bootstrap/withContext.tsx b/src/core/client/framework/lib/bootstrap/withContext.tsx index 82ae2695f..677da236a 100644 --- a/src/core/client/framework/lib/bootstrap/withContext.tsx +++ b/src/core/client/framework/lib/bootstrap/withContext.tsx @@ -1,5 +1,9 @@ import * as React from "react"; -import { hoistStatics, InferableComponentEnhancer } from "recompose"; +import { + hoistStatics, + InferableComponentEnhancer, + wrapDisplayName, +} from "recompose"; import { TalkContext, TalkContextConsumer } from "./TalkContext"; @@ -12,11 +16,17 @@ function withContext( propsCallback: (context: TalkContext) => T ): InferableComponentEnhancer { return hoistStatics( - (WrappedComponent: React.ComponentType) => (props: any) => ( - - {context => } - - ) + (WrappedComponent: React.ComponentType) => { + const Component: React.StatelessComponent = props => ( + + {context => ( + + )} + + ); + Component.displayName = wrapDisplayName(WrappedComponent, "withContext"); + return Component; + } ); } diff --git a/src/core/client/framework/lib/messages.tsx b/src/core/client/framework/lib/messages.tsx index c8234c670..316cd6e8a 100644 --- a/src/core/client/framework/lib/messages.tsx +++ b/src/core/client/framework/lib/messages.tsx @@ -13,7 +13,7 @@ export const VALIDATION_REQUIRED = () => ( ); export const VALIDATION_TOO_SHORT = () => ( - + This field is too short. ); diff --git a/src/core/client/framework/lib/relay/QueryRenderer.tsx b/src/core/client/framework/lib/relay/QueryRenderer.tsx index 30a42dbe0..a5845c0b6 100644 --- a/src/core/client/framework/lib/relay/QueryRenderer.tsx +++ b/src/core/client/framework/lib/relay/QueryRenderer.tsx @@ -1,24 +1,18 @@ import React, { Component } from "react"; -import { QueryRenderer } from "react-relay"; -import { CacheConfig, GraphQLTaggedNode, RerunParam } from "relay-runtime"; +import { + QueryRenderer, + QueryRendererProps as QueryRendererPropsOrig, +} from "react-relay"; + +import { Omit } from "talk-framework/types"; import { TalkContextConsumer } from "../bootstrap/TalkContext"; -// Taken from relay types and added Generic support for Variables and Response -export interface QueryRendererProps { - cacheConfig?: CacheConfig; - query?: GraphQLTaggedNode | null; - render(readyState: ReadyState): React.ReactElement | undefined | null; - variables: V; - rerunParamExperimental?: RerunParam; -} - -// Taken from relay types and added Generic support for Variables and Response -export interface ReadyState { - error: Error | undefined | null; - props: R | undefined | null; - retry?(): void; -} +// Omit environment as we are passing this from the context. +export type QueryRendererProps = Omit< + QueryRendererPropsOrig, + "environment" +>; /** * TalkQueryRenderer is a wrappper around Relay's `QueryRenderer`. diff --git a/src/core/client/framework/lib/relay/createMutationContainer.tsx b/src/core/client/framework/lib/relay/createMutationContainer.tsx index 3e3d8bf60..d0a91e92d 100644 --- a/src/core/client/framework/lib/relay/createMutationContainer.tsx +++ b/src/core/client/framework/lib/relay/createMutationContainer.tsx @@ -1,5 +1,10 @@ import * as React from "react"; -import { compose, hoistStatics, InferableComponentEnhancer } from "recompose"; +import { + compose, + hoistStatics, + InferableComponentEnhancer, + wrapDisplayName, +} from "recompose"; import { Environment } from "relay-runtime"; import { withContext } from "../bootstrap"; @@ -19,6 +24,11 @@ function createMutationContainer( withContext(({ relayEnvironment }) => ({ relayEnvironment })), hoistStatics((WrappedComponent: React.ComponentType) => { class CreateMutationContainer extends React.Component { + public static displayName = wrapDisplayName( + WrappedComponent, + "createMutationContainer" + ); + private commit = (input: I) => { return commit(this.props.relayEnvironment, input); }; diff --git a/src/core/client/framework/lib/relay/index.ts b/src/core/client/framework/lib/relay/index.ts index e55279f7c..43dca5178 100644 --- a/src/core/client/framework/lib/relay/index.ts +++ b/src/core/client/framework/lib/relay/index.ts @@ -7,6 +7,7 @@ export { default as QueryRenderer } from "./QueryRenderer"; export * from "./QueryRenderer"; export { default as createMutationContainer } from "./createMutationContainer"; export { default as createAndRetain } from "./createAndRetain"; +export { default as wrapFetchWithLogger } from "./wrapFetchWithLogger"; export { commitMutationPromise, commitMutationPromiseNormalized, diff --git a/src/core/client/framework/lib/relay/withFragmentContainer.ts b/src/core/client/framework/lib/relay/withFragmentContainer.ts index c4636fe3c..2541b2001 100644 --- a/src/core/client/framework/lib/relay/withFragmentContainer.ts +++ b/src/core/client/framework/lib/relay/withFragmentContainer.ts @@ -6,7 +6,7 @@ import { InferableComponentEnhancerWithProps } from "recompose"; * from Relay. */ export default ( - fragmentSpec: GraphQLTaggedNode + fragmentSpec: { [P in keyof T]: GraphQLTaggedNode } ): InferableComponentEnhancerWithProps => ( component: React.ComponentType ) => createFragmentContainer(component, fragmentSpec) as any; diff --git a/src/core/client/framework/lib/relay/withLocalStateContainer.tsx b/src/core/client/framework/lib/relay/withLocalStateContainer.tsx index d08aa5d55..9de4180bb 100644 --- a/src/core/client/framework/lib/relay/withLocalStateContainer.tsx +++ b/src/core/client/framework/lib/relay/withLocalStateContainer.tsx @@ -1,6 +1,11 @@ import * as React from "react"; import { compose, hoistStatics, InferableComponentEnhancer } from "recompose"; -import { CSelector, CSnapshot, Environment } from "relay-runtime"; +import { + CSelector, + CSnapshot, + Environment, + GraphQLTaggedNode, +} from "relay-runtime"; import { withContext } from "../bootstrap"; @@ -25,7 +30,7 @@ export const LOCAL_ID = "client:root.local"; * must have the `LOCAL_ID`. */ function withLocalStateContainer( - fragmentSpec: any + fragmentSpec: GraphQLTaggedNode ): InferableComponentEnhancer<{ local: T }> { return compose( withContext(({ relayEnvironment }) => ({ relayEnvironment })), @@ -33,7 +38,7 @@ function withLocalStateContainer( class LocalStateContainer extends React.Component { constructor(props: Props) { super(props); - const fragment = fragmentSpec.data().default; + const fragment = (fragmentSpec as any).data().default; if (fragment.kind !== "Fragment") { throw new Error("Expected fragment"); } diff --git a/src/core/client/framework/lib/relay/withPaginationContainer.ts b/src/core/client/framework/lib/relay/withPaginationContainer.ts index 5f768c0a7..bb2740d01 100644 --- a/src/core/client/framework/lib/relay/withPaginationContainer.ts +++ b/src/core/client/framework/lib/relay/withPaginationContainer.ts @@ -10,9 +10,13 @@ import { InferableComponentEnhancerWithProps } from "recompose"; * withPaginationContainer is a curried version of `createPaginationContainers` * from Relay. */ -export default ( - fragmentSpec: GraphQLTaggedNode, - connectionConfig: ConnectionConfig +export default ( + fragmentSpec: { [P in keyof T]: GraphQLTaggedNode }, + connectionConfig: ConnectionConfig< + InnerProps, + FragmentVariables, + QueryVariables + > ): InferableComponentEnhancerWithProps< T & { relay: RelayPaginationProp }, { [P in keyof T]: any } diff --git a/src/core/client/framework/lib/relay/withRefetchContainer.ts b/src/core/client/framework/lib/relay/withRefetchContainer.ts index 7d11e0e16..9e9989758 100644 --- a/src/core/client/framework/lib/relay/withRefetchContainer.ts +++ b/src/core/client/framework/lib/relay/withRefetchContainer.ts @@ -10,7 +10,7 @@ import { InferableComponentEnhancerWithProps } from "recompose"; * from Relay. */ export default ( - fragmentSpec: GraphQLTaggedNode, + fragmentSpec: { [P in keyof T]: GraphQLTaggedNode }, refetchQuery: GraphQLTaggedNode ): InferableComponentEnhancerWithProps< T & { relay: RelayRefetchProp }, diff --git a/src/core/client/framework/lib/relay/wrapFetchWithLogger.ts b/src/core/client/framework/lib/relay/wrapFetchWithLogger.ts new file mode 100644 index 000000000..d320e8ab0 --- /dev/null +++ b/src/core/client/framework/lib/relay/wrapFetchWithLogger.ts @@ -0,0 +1,25 @@ +import { FetchFunction } from "relay-runtime"; + +/** + * Decorates the fetch function with error logging. + * Intended for testing purposes. + */ +export default function wrapFetchWithLogger( + fetch: FetchFunction, + logResult?: boolean +): FetchFunction { + return async (...args: any[]) => { + try { + const result = await (fetch as any)(...args); + if (logResult) { + // tslint:disable-next-line:no-console + console.log(JSON.stringify(result)); + } + return result; + } catch (err) { + // tslint:disable-next-line:no-console + console.error(err); + throw err; + } + }; +} diff --git a/src/core/client/stream/.babelrc.js b/src/core/client/stream/.babelrc.js index d2601c757..11bf9a670 100644 --- a/src/core/client/stream/.babelrc.js +++ b/src/core/client/stream/.babelrc.js @@ -1,5 +1,4 @@ const path = require("path"); - module.exports = { extends: "../.babelrc.js", plugins: [ diff --git a/src/core/client/stream/components/App.tsx b/src/core/client/stream/components/App.tsx index dde8e386c..eb59b588e 100644 --- a/src/core/client/stream/components/App.tsx +++ b/src/core/client/stream/components/App.tsx @@ -3,30 +3,17 @@ import { StatelessComponent } from "react"; import { Center } from "talk-ui/components"; -import AssetListContainer from "../containers/AssetListContainer"; -import PostCommentFormContainer from "../containers/PostCommentFormContainer"; import StreamContainer from "../containers/StreamContainer"; -import Logo from "./Logo"; export interface AppProps { - assets?: any | null; - asset?: { - id: string; - isClosed: boolean; - comments: any | null; - } | null; + asset: {} | null; } const App: StatelessComponent = props => { - if (props.assets) { - return ; - } if (props.asset) { return (
- - - +
); } diff --git a/src/core/client/stream/components/AssetList.tsx b/src/core/client/stream/components/AssetList.tsx deleted file mode 100644 index a82c34fe6..000000000 --- a/src/core/client/stream/components/AssetList.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from "react"; -import { StatelessComponent } from "react"; - -export interface AssetListProps { - assets: ReadonlyArray<{ id: string; title: string | null }>; -} - -const AssetList: StatelessComponent = props => { - return ( -
- {props.assets.map(asset =>
{asset.title}
)} -
- ); -}; - -export default AssetList; diff --git a/src/core/client/stream/components/Comment.css b/src/core/client/stream/components/Comment.css index d43780d8f..c7f11300a 100644 --- a/src/core/client/stream/components/Comment.css +++ b/src/core/client/stream/components/Comment.css @@ -6,6 +6,6 @@ margin-bottom: calc(2px * $spacing-unit); } -.author { - font-weight: $font-weight-medium; +.topBar { + margin-bottom: calc(1px * $spacing-unit); } diff --git a/src/core/client/stream/components/Comment.spec.tsx b/src/core/client/stream/components/Comment.spec.tsx index 6008bfc3b..a73e04730 100644 --- a/src/core/client/stream/components/Comment.spec.tsx +++ b/src/core/client/stream/components/Comment.spec.tsx @@ -1,5 +1,6 @@ +import { shallow } from "enzyme"; import React from "react"; -import { createRenderer } from "react-test-renderer/shallow"; + import Comment from "./Comment"; it("renders username and body", () => { @@ -9,9 +10,8 @@ it("renders username and body", () => { }, body: "Woof", }; - const renderer = createRenderer(); - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); }); it("renders with gutterBottom", () => { @@ -22,7 +22,6 @@ it("renders with gutterBottom", () => { body: "Woof", gutterBottom: true, }; - const renderer = createRenderer(); - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); }); diff --git a/src/core/client/stream/components/Comment.tsx b/src/core/client/stream/components/Comment.tsx index c1b3f9303..bd22c4ffc 100644 --- a/src/core/client/stream/components/Comment.tsx +++ b/src/core/client/stream/components/Comment.tsx @@ -5,6 +5,7 @@ import { StatelessComponent } from "react"; import { Typography } from "talk-ui/components"; import * as styles from "./Comment.css"; +import Username from "./Username"; export interface CommentProps { className?: string; @@ -20,10 +21,10 @@ const Comment: StatelessComponent = props => { [styles.gutterBottom]: props.gutterBottom, }); return ( -
- - {props.author && props.author.username} - +
+
+ {props.author && {props.author.username}} +
{props.body}
); diff --git a/src/core/client/stream/components/Indent.css b/src/core/client/stream/components/Indent.css new file mode 100644 index 000000000..dcf73c502 --- /dev/null +++ b/src/core/client/stream/components/Indent.css @@ -0,0 +1,8 @@ +.root { + border-left: 3px solid; + padding-left: calc(2px * $spacing-unit); +} + +.level0 { + border-color: $palette-secondary-darkest; +} diff --git a/src/core/client/stream/components/Indent.tsx b/src/core/client/stream/components/Indent.tsx new file mode 100644 index 000000000..7ea8fddf3 --- /dev/null +++ b/src/core/client/stream/components/Indent.tsx @@ -0,0 +1,15 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import * as styles from "./Indent.css"; + +export interface IndentProps { + level?: number; + children: React.ReactNode; +} + +const Indent: StatelessComponent = props => { + return
{props.children}
; +}; + +export default Indent; diff --git a/src/core/client/stream/components/PostCommentForm.css b/src/core/client/stream/components/PostCommentForm.css index a536dcc8b..c776ca7c8 100644 --- a/src/core/client/stream/components/PostCommentForm.css +++ b/src/core/client/stream/components/PostCommentForm.css @@ -7,6 +7,7 @@ margin-bottom: calc(2px * $spacing-unit); } -.postButton { - float: right; -} +.postButtonContainer { + display: flex; + justify-content: flex-end; +} \ No newline at end of file diff --git a/src/core/client/stream/components/PostCommentForm.tsx b/src/core/client/stream/components/PostCommentForm.tsx index 2187994be..09db70e39 100644 --- a/src/core/client/stream/components/PostCommentForm.tsx +++ b/src/core/client/stream/components/PostCommentForm.tsx @@ -39,11 +39,13 @@ const PostCommentForm: StatelessComponent = props => (
)} - - - +
+ + + +
)} diff --git a/src/core/client/stream/components/ReplyList.spec.tsx b/src/core/client/stream/components/ReplyList.spec.tsx new file mode 100644 index 000000000..12b673732 --- /dev/null +++ b/src/core/client/stream/components/ReplyList.spec.tsx @@ -0,0 +1,40 @@ +import { shallow } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; +import sinon, { SinonSpy } from "sinon"; + +import ReplyList, { ReplyListProps } from "./ReplyList"; + +it("renders correctly", () => { + const props: ReplyListProps = { + commentID: "comment-id", + comments: [{ id: "comment-1" }, { id: "comment-2" }], + onShowAll: noop, + hasMore: false, + disableShowAll: false, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +describe("when there is more", () => { + const props: ReplyListProps = { + commentID: "comment-id", + comments: [{ id: "comment-1" }, { id: "comment-2" }], + onShowAll: sinon.spy(), + hasMore: true, + disableShowAll: false, + }; + + const wrapper = shallow(); + it("renders a load more button", () => { + expect(wrapper).toMatchSnapshot(); + }); + + it("calls onLoadMore", () => { + wrapper + .find("#talk-comments-replyList-showAll--comment-id") + .simulate("click"); + expect((props.onShowAll as SinonSpy).calledOnce).toBe(true); + }); +}); diff --git a/src/core/client/stream/components/ReplyList.tsx b/src/core/client/stream/components/ReplyList.tsx new file mode 100644 index 000000000..ec366771c --- /dev/null +++ b/src/core/client/stream/components/ReplyList.tsx @@ -0,0 +1,45 @@ +import { Localized } from "fluent-react/compat"; +import * as React from "react"; +import { StatelessComponent } from "react"; + +import { Button } from "talk-ui/components"; + +import CommentContainer from "../containers/CommentContainer"; +import Indent from "./Indent"; + +export interface ReplyListProps { + commentID: string; + comments: ReadonlyArray<{ id: string }>; + onShowAll: () => void; + hasMore: boolean; + disableShowAll: boolean; +} + +const ReplyList: StatelessComponent = props => { + return ( + +
+ {props.comments.map(comment => ( + + ))} + {props.hasMore && ( + + + + )} +
+
+ ); +}; + +export default ReplyList; diff --git a/src/core/client/stream/components/Stream.spec.tsx b/src/core/client/stream/components/Stream.spec.tsx new file mode 100644 index 000000000..05d30d66e --- /dev/null +++ b/src/core/client/stream/components/Stream.spec.tsx @@ -0,0 +1,45 @@ +import { shallow } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; +import sinon from "sinon"; + +import Stream, { StreamProps } from "./Stream"; + +it("renders correctly", () => { + const props: StreamProps = { + assetID: "asset-id", + isClosed: false, + comments: [{ id: "comment-1" }, { id: "comment-2" }], + onLoadMore: noop, + disableLoadMore: false, + hasMore: false, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +describe("when there is more", () => { + const props = { + assetID: "asset-id", + isClosed: false, + comments: [{ id: "comment-1" }, { id: "comment-2" }], + onLoadMore: sinon.spy(), + disableLoadMore: false, + hasMore: true, + }; + + const wrapper = shallow(); + it("renders a load more button", () => { + expect(wrapper).toMatchSnapshot(); + }); + + it("calls onLoadMore", () => { + wrapper.find("#talk-comments-stream-loadMore").simulate("click"); + expect(props.onLoadMore.calledOnce).toBe(true); + }); + + 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 c42bb21b3..a14816453 100644 --- a/src/core/client/stream/components/Stream.tsx +++ b/src/core/client/stream/components/Stream.tsx @@ -1,18 +1,51 @@ +import { Localized } from "fluent-react/compat"; import * as React from "react"; import { StatelessComponent } from "react"; +import { Button } from "talk-ui/components"; + import CommentContainer from "../containers/CommentContainer"; +import PostCommentFormContainer from "../containers/PostCommentFormContainer"; +import ReplyListContainer from "../containers/ReplyListContainer"; +import Logo from "./Logo"; export interface StreamProps { + assetID: string; + isClosed: boolean; comments: ReadonlyArray<{ id: string }>; + onLoadMore: () => void; + hasMore: boolean; + disableLoadMore: boolean; } const Stream: StatelessComponent = props => { return (
- {props.comments.map(comment => ( - - ))} + + +
+ {props.comments.map(comment => ( +
+ + +
+ ))} + {props.hasMore && ( + + + + )} +
); }; diff --git a/src/core/client/stream/components/Username.css b/src/core/client/stream/components/Username.css new file mode 100644 index 000000000..9cb1baa15 --- /dev/null +++ b/src/core/client/stream/components/Username.css @@ -0,0 +1,3 @@ +.root { + composes: heading4 from "talk-ui/shared/typography.css"; +} diff --git a/src/core/client/stream/components/Username.spec.tsx b/src/core/client/stream/components/Username.spec.tsx new file mode 100644 index 000000000..c4f1c1dfe --- /dev/null +++ b/src/core/client/stream/components/Username.spec.tsx @@ -0,0 +1,12 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import Username from "./Username"; + +it("renders correctly", () => { + const props = { + children: "Marvin", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/components/Username.tsx b/src/core/client/stream/components/Username.tsx new file mode 100644 index 000000000..117ebdb3d --- /dev/null +++ b/src/core/client/stream/components/Username.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { StatelessComponent } from "react"; + +import * as styles from "./Username.css"; + +export interface CommentProps { + children: string; +} + +const Username: StatelessComponent = props => { + return {props.children}; +}; + +export default Username; diff --git a/src/core/client/stream/components/__snapshots__/Comment.spec.tsx.snap b/src/core/client/stream/components/__snapshots__/Comment.spec.tsx.snap index 4b009e257..9aafeaf5c 100644 --- a/src/core/client/stream/components/__snapshots__/Comment.spec.tsx.snap +++ b/src/core/client/stream/components/__snapshots__/Comment.spec.tsx.snap @@ -2,14 +2,16 @@ exports[`renders username and body 1`] = `
- - Marvin - + + Marvin + +
Woof @@ -18,14 +20,16 @@ exports[`renders username and body 1`] = ` exports[`renders with gutterBottom 1`] = `
- - Marvin - + + Marvin + +
Woof diff --git a/src/core/client/stream/components/__snapshots__/ReplyList.spec.tsx.snap b/src/core/client/stream/components/__snapshots__/ReplyList.spec.tsx.snap new file mode 100644 index 000000000..36fb0c110 --- /dev/null +++ b/src/core/client/stream/components/__snapshots__/ReplyList.spec.tsx.snap @@ -0,0 +1,72 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + +
+ + +
+
+`; + +exports[`when there is more renders a load more button 1`] = ` + +
+ + + + + Show All Replies + + +
+
+`; diff --git a/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap b/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap new file mode 100644 index 000000000..65c508b25 --- /dev/null +++ b/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap @@ -0,0 +1,196 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+ + +
+
+ + +
+
+ + +
+
+
+`; + +exports[`when there is more disables load more button 1`] = ` +
+ + +
+
+ + +
+
+ + +
+ + + Load More + + +
+
+`; + +exports[`when there is more renders a load more button 1`] = ` +
+ + +
+
+ + +
+
+ + +
+ + + Load More + + +
+
+`; diff --git a/src/core/client/stream/components/__snapshots__/Username.spec.tsx.snap b/src/core/client/stream/components/__snapshots__/Username.spec.tsx.snap new file mode 100644 index 000000000..27d624f2f --- /dev/null +++ b/src/core/client/stream/components/__snapshots__/Username.spec.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + Marvin + +`; diff --git a/src/core/client/stream/containers/AppContainer.tsx b/src/core/client/stream/containers/AppContainer.tsx index 02abbf314..8b84660ca 100644 --- a/src/core/client/stream/containers/AppContainer.tsx +++ b/src/core/client/stream/containers/AppContainer.tsx @@ -15,26 +15,16 @@ const AppContainer: StatelessComponent = props => { return ; }; -const enhanced = withFragmentContainer<{ data: Data }>( - graphql` +const enhanced = withFragmentContainer<{ data: Data }>({ + data: graphql` fragment AppContainer on Query - @argumentDefinitions( - assetID: { type: "ID!" } - showAssetList: { type: "Boolean!" } - ) { - assets @include(if: $showAssetList) { - ...AssetListContainer_assets - } - asset(id: $assetID) @skip(if: $showAssetList) { - id - isClosed - comments { - ...StreamContainer_comments - } + @argumentDefinitions(assetID: { type: "ID!" }) { + asset(id: $assetID) { + ...StreamContainer_asset } } - ` -)(AppContainer); + `, +})(AppContainer); export type AppContainerProps = PropTypesOf; export default enhanced; diff --git a/src/core/client/stream/containers/AssetListContainer.tsx b/src/core/client/stream/containers/AssetListContainer.tsx deleted file mode 100644 index 6e441f00e..000000000 --- a/src/core/client/stream/containers/AssetListContainer.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { StatelessComponent } from "react"; -import { graphql } from "react-relay"; - -import { withFragmentContainer } from "talk-framework/lib/relay"; -import { PropTypesOf } from "talk-framework/types"; -import { AssetListContainer_assets as Data } from "talk-stream/__generated__/AssetListContainer_assets.graphql"; - -import AssetList from "../components/AssetList"; - -interface InnerProps { - assets: Data; -} - -const AssetListContainer: StatelessComponent = props => { - const assets = props.assets.edges.map(edge => edge.node); - return ; -}; - -const enhanced = withFragmentContainer<{ assets: Data }>( - graphql` - fragment AssetListContainer_assets on AssetsConnection { - edges { - node { - id - title - } - } - } - ` -)(AssetListContainer); - -export type AssetListContainerProps = PropTypesOf; -export default enhanced; diff --git a/src/core/client/stream/containers/CommentContainer.spec.tsx b/src/core/client/stream/containers/CommentContainer.spec.tsx new file mode 100644 index 000000000..94ffb7392 --- /dev/null +++ b/src/core/client/stream/containers/CommentContainer.spec.tsx @@ -0,0 +1,18 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { CommentContainer } from "./CommentContainer"; + +it("renders username and body", () => { + const props = { + data: { + author: { + username: "Marvin", + }, + body: "Woof", + }, + }; + + 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 9a4d03cd8..9f20ecfc5 100644 --- a/src/core/client/stream/containers/CommentContainer.tsx +++ b/src/core/client/stream/containers/CommentContainer.tsx @@ -9,21 +9,28 @@ import Comment, { CommentProps } from "../components/Comment"; type InnerProps = { data: Data } & Omit; -const CommentContainer: StatelessComponent = props => { +// tslint:disable-next-line:no-unused-expression +graphql` + fragment CommentContainer_comment on Comment { + author { + username + } + body + } +`; + +export const CommentContainer: StatelessComponent = props => { const { data, ...rest } = props; return ; }; -const enhanced = withFragmentContainer<{ data: Data }>( - graphql` +const enhanced = withFragmentContainer<{ data: Data }>({ + data: graphql` fragment CommentContainer on Comment { - author { - username - } - body + ...CommentContainer_comment @relay(mask: false) } - ` -)(CommentContainer); + `, +})(CommentContainer); export type CommentContainerProps = PropTypesOf; export default enhanced; diff --git a/src/core/client/stream/containers/PostCommentFormContainer.tsx b/src/core/client/stream/containers/PostCommentFormContainer.tsx index d2da26f64..bf4ab8cf9 100644 --- a/src/core/client/stream/containers/PostCommentFormContainer.tsx +++ b/src/core/client/stream/containers/PostCommentFormContainer.tsx @@ -25,6 +25,8 @@ class PostCommentFormContainer extends Component { if (error instanceof BadUserInputError) { return error.invalidArgsLocalized; } + // tslint:disable-next-line:no-console + console.error(error); } return undefined; }; diff --git a/src/core/client/stream/containers/ReplyListContainer.spec.tsx b/src/core/client/stream/containers/ReplyListContainer.spec.tsx new file mode 100644 index 000000000..75cb129ca --- /dev/null +++ b/src/core/client/stream/containers/ReplyListContainer.spec.tsx @@ -0,0 +1,86 @@ +import { shallow, ShallowWrapper } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import ReplyList from "../components/ReplyList"; +import { ReplyListContainer } from "./ReplyListContainer"; + +it("renders correctly", () => { + const props: any = { + comment: { + id: "comment-id", + replies: { + edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }], + }, + }, + relay: { + hasMore: noop, + isLoading: noop, + }, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders correctly when replies are null", () => { + const props: any = { + comment: { + id: "comment-id", + replies: null, + }, + relay: { + hasMore: noop, + isLoading: noop, + }, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +describe("when has more replies", () => { + let finishLoading: ((error?: Error) => void) | null = null; + const props: PropTypesOf = { + comment: { + id: "comment-id", + replies: { + edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }], + }, + }, + relay: { + hasMore: () => true, + isLoading: () => false, + loadMore: (_: any, callback: () => void) => (finishLoading = callback), + } as any, + }; + + let wrapper: ShallowWrapper; + + beforeAll(() => (wrapper = shallow())); + + it("renders hasMore", () => { + expect(wrapper).toMatchSnapshot(); + }); + + describe("when showing all", () => { + beforeAll(() => { + wrapper + .find(ReplyList) + .props() + .onShowAll(); + }); + it("calls relay loadMore", () => { + expect(finishLoading).not.toBeNull(); + }); + it("disables show all button", () => { + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + }); + it("enable show all button after loading is done", () => { + finishLoading!(); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/src/core/client/stream/containers/ReplyListContainer.tsx b/src/core/client/stream/containers/ReplyListContainer.tsx new file mode 100644 index 000000000..d147edb1c --- /dev/null +++ b/src/core/client/stream/containers/ReplyListContainer.tsx @@ -0,0 +1,132 @@ +import React from "react"; +import { graphql, RelayPaginationProp } from "react-relay"; + +import { withPaginationContainer } from "talk-framework/lib/relay"; +import { PropTypesOf } from "talk-framework/types"; +import { ReplyListContainer_comment as Data } from "talk-stream/__generated__/ReplyListContainer_comment.graphql"; +import { + COMMENT_SORT, + ReplyListContainerPaginationQueryVariables, +} from "talk-stream/__generated__/ReplyListContainerPaginationQuery.graphql"; + +import ReplyList from "../components/ReplyList"; + +export interface InnerProps { + comment: Data; + relay: RelayPaginationProp; +} + +export class ReplyListContainer extends React.Component { + public state = { + disableShowAll: false, + }; + + public render() { + if (this.props.comment.replies === null) { + return null; + } + const comments = this.props.comment.replies.edges.map(edge => edge.node); + return ( + + ); + } + + private showAll = () => { + if (!this.props.relay.hasMore() || this.props.relay.isLoading()) { + return; + } + + this.setState({ disableShowAll: true }); + this.props.relay.loadMore( + 999999999, // Fetch All Replies + error => { + this.setState({ disableShowAll: false }); + if (error) { + // tslint:disable-next-line:no-console + console.error(error); + } + } + ); + }; +} + +// TODO: (cvle) This should be autogenerated. +interface FragmentVariables { + count: number; + cursor?: string; + orderBy: COMMENT_SORT; +} + +const enhanced = withPaginationContainer< + { comment: Data }, + InnerProps, + FragmentVariables, + ReplyListContainerPaginationQueryVariables +>( + { + comment: graphql` + fragment ReplyListContainer_comment on Comment + @argumentDefinitions( + count: { type: "Int!", defaultValue: 5 } + cursor: { type: "Cursor" } + orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC } + ) { + id + replies(first: $count, after: $cursor, orderBy: $orderBy) + @connection(key: "ReplyList_replies") { + edges { + node { + id + ...CommentContainer + } + } + } + } + `, + }, + { + direction: "forward", + getConnectionFromProps(props) { + return props.comment && props.comment.replies; + }, + // This is also the default implementation of `getFragmentVariables` if it isn't provided. + getFragmentVariables(prevVars, totalCount) { + return { + ...prevVars, + count: totalCount, + }; + }, + getVariables(props, { count, cursor }, fragmentVariables) { + return { + count, + cursor, + orderBy: fragmentVariables.orderBy, + commentID: props.comment.id, + }; + }, + query: graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query ReplyListContainerPaginationQuery( + $count: Int! + $cursor: Cursor + $orderBy: COMMENT_SORT! + $commentID: ID! + ) { + comment(id: $commentID) { + ...ReplyListContainer_comment + @arguments(count: $count, cursor: $cursor, orderBy: $orderBy) + } + } + `, + } +)(ReplyListContainer); + +export type ReplyListContainerProps = PropTypesOf; +export default enhanced; diff --git a/src/core/client/stream/containers/StreamContainer.spec.tsx b/src/core/client/stream/containers/StreamContainer.spec.tsx new file mode 100644 index 000000000..2e19bda86 --- /dev/null +++ b/src/core/client/stream/containers/StreamContainer.spec.tsx @@ -0,0 +1,73 @@ +import { shallow, ShallowWrapper } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import Stream from "../components/Stream"; +import { StreamContainer } from "./StreamContainer"; + +it("renders correctly", () => { + const props: PropTypesOf = { + asset: { + id: "asset-id", + isClosed: false, + comments: { + edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }], + }, + }, + relay: { + hasMore: noop, + isLoading: noop, + } as any, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +describe("when has more comments", () => { + let finishLoading: ((error?: Error) => void) | null = null; + const props: PropTypesOf = { + asset: { + id: "asset-id", + isClosed: false, + comments: { + edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }], + }, + }, + relay: { + hasMore: () => true, + isLoading: () => false, + loadMore: (_: any, callback: () => void) => (finishLoading = callback), + } as any, + }; + + let wrapper: ShallowWrapper; + + beforeAll(() => (wrapper = shallow())); + + it("renders hasMore", () => { + expect(wrapper).toMatchSnapshot(); + }); + + describe("when loading more", () => { + beforeAll(() => { + wrapper + .find(Stream) + .props() + .onLoadMore(); + }); + it("calls relay loadMore", () => { + expect(finishLoading).not.toBeNull(); + }); + it("disables load more button", () => { + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + }); + it("enable load more button after loading is done", () => { + finishLoading!(); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/src/core/client/stream/containers/StreamContainer.tsx b/src/core/client/stream/containers/StreamContainer.tsx index 771c25efa..d81319deb 100644 --- a/src/core/client/stream/containers/StreamContainer.tsx +++ b/src/core/client/stream/containers/StreamContainer.tsx @@ -1,32 +1,132 @@ -import React, { StatelessComponent } from "react"; -import { graphql } from "react-relay"; +import React from "react"; +import { graphql, RelayPaginationProp } from "react-relay"; -import { withFragmentContainer } from "talk-framework/lib/relay"; +import { withPaginationContainer } from "talk-framework/lib/relay"; import { PropTypesOf } from "talk-framework/types"; -import { StreamContainer_comments as Data } from "talk-stream/__generated__/StreamContainer_comments.graphql"; +import { StreamContainer_asset as Data } from "talk-stream/__generated__/StreamContainer_asset.graphql"; +import { + COMMENT_SORT, + StreamContainerPaginationQueryVariables, +} from "talk-stream/__generated__/StreamContainerPaginationQuery.graphql"; import Stream from "../components/Stream"; interface InnerProps { - comments: Data; + asset: Data; + relay: RelayPaginationProp; } -const StreamContainer: StatelessComponent = props => { - const comments = props.comments.edges.map(edge => edge.node); - return ; -}; +export class StreamContainer extends React.Component { + public state = { + disableLoadMore: false, + }; -const enhanced = withFragmentContainer<{ comments: Data }>( - graphql` - fragment StreamContainer_comments on CommentsConnection { - edges { - node { - id - ...CommentContainer + public render() { + const comments = this.props.asset.comments.edges.map(edge => edge.node); + return ( + + ); + } + + private loadMore = () => { + if (!this.props.relay.hasMore() || this.props.relay.isLoading()) { + return; + } + this.setState({ disableLoadMore: true }); + this.props.relay.loadMore( + 10, // Fetch the next 10 feed items + error => { + this.setState({ disableLoadMore: false }); + if (error) { + // tslint:disable-next-line:no-console + console.error(error); } } - } - ` + ); + }; +} + +// TODO: (cvle) This should be autogenerated. +interface FragmentVariables { + count: number; + cursor?: string; + orderBy: COMMENT_SORT; +} + +const enhanced = withPaginationContainer< + { asset: Data }, + InnerProps, + FragmentVariables, + StreamContainerPaginationQueryVariables +>( + { + asset: graphql` + fragment StreamContainer_asset on Asset + @argumentDefinitions( + count: { type: "Int!", defaultValue: 5 } + cursor: { type: "Cursor" } + orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_DESC } + ) { + id + isClosed + comments(first: $count, after: $cursor, orderBy: $orderBy) + @connection(key: "Stream_comments") { + edges { + node { + id + ...CommentContainer + ...ReplyListContainer_comment + } + } + } + } + `, + }, + { + direction: "forward", + getConnectionFromProps(props) { + return props.asset && props.asset.comments; + }, + // This is also the default implementation of `getFragmentVariables` if it isn't provided. + getFragmentVariables(prevVars, totalCount) { + return { + ...prevVars, + count: totalCount, + }; + }, + getVariables(props, { count, cursor }, fragmentVariables) { + return { + count, + cursor, + orderBy: fragmentVariables.orderBy, + // assetID isn't specified as an @argument for the fragment, but it should be a + // variable available for the fragment under the query root. + assetID: props.asset.id, + }; + }, + query: graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query StreamContainerPaginationQuery( + $count: Int! + $cursor: Cursor + $orderBy: COMMENT_SORT! + $assetID: ID! + ) { + asset(id: $assetID) { + ...StreamContainer_asset + @arguments(count: $count, cursor: $cursor, orderBy: $orderBy) + } + } + `, + } )(StreamContainer); export type StreamContainerProps = PropTypesOf; diff --git a/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap b/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap new file mode 100644 index 000000000..6ce053179 --- /dev/null +++ b/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders username and body 1`] = ` + +`; diff --git a/src/core/client/stream/containers/__snapshots__/ReplyListContainer.spec.tsx.snap b/src/core/client/stream/containers/__snapshots__/ReplyListContainer.spec.tsx.snap new file mode 100644 index 000000000..52aa9c57b --- /dev/null +++ b/src/core/client/stream/containers/__snapshots__/ReplyListContainer.spec.tsx.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + +`; + +exports[`renders correctly when replies are null 1`] = `""`; + +exports[`when has more replies renders hasMore 1`] = ` + +`; + +exports[`when has more replies when showing all disables show all button 1`] = ` + +`; + +exports[`when has more replies when showing all enable show all button after loading is done 1`] = ` + +`; diff --git a/src/core/client/stream/containers/__snapshots__/StreamContainer.spec.tsx.snap b/src/core/client/stream/containers/__snapshots__/StreamContainer.spec.tsx.snap new file mode 100644 index 000000000..e3813397c --- /dev/null +++ b/src/core/client/stream/containers/__snapshots__/StreamContainer.spec.tsx.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + +`; + +exports[`when has more comments renders hasMore 1`] = ` + +`; + +exports[`when has more comments when loading more disables load more button 1`] = ` + +`; + +exports[`when has more comments when loading more enable load more button after loading is done 1`] = ` + +`; diff --git a/src/core/client/stream/queries/AppQuery.spec.tsx b/src/core/client/stream/queries/AppQuery.spec.tsx new file mode 100644 index 000000000..a301a76b7 --- /dev/null +++ b/src/core/client/stream/queries/AppQuery.spec.tsx @@ -0,0 +1,31 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { render } from "./AppQuery"; + +it("renders app", () => { + const data = { + props: {} as any, + error: null, + }; + const wrapper = shallow(React.createElement(() => render(data))); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders loading", () => { + const data = { + props: null, + error: null, + }; + const wrapper = shallow(React.createElement(() => render(data))); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders error", () => { + const data = { + props: null, + error: new Error("error"), + }; + const wrapper = shallow(React.createElement(() => render(data))); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/queries/AppQuery.tsx b/src/core/client/stream/queries/AppQuery.tsx index 4dc61115c..3fe95b6d9 100644 --- a/src/core/client/stream/queries/AppQuery.tsx +++ b/src/core/client/stream/queries/AppQuery.tsx @@ -1,10 +1,10 @@ import * as React from "react"; import { StatelessComponent } from "react"; +import { ReadyState } from "react-relay"; import { graphql, QueryRenderer, - ReadyState, withLocalStateContainer, } from "talk-framework/lib/relay"; import { @@ -15,7 +15,7 @@ import { AppQueryLocal as Local } from "talk-stream/__generated__/AppQueryLocal. import AppContainer from "../containers/AppContainer"; -const render = ({ error, props }: ReadyState) => { +export const render = ({ error, props }: ReadyState) => { if (error) { return
{error.message}
; } @@ -33,16 +33,12 @@ const AppQuery: StatelessComponent = props => { return ( query={graphql` - query AppQuery($showAssetList: Boolean!, $assetID: ID!) { - ...AppContainer - @arguments(showAssetList: $showAssetList, assetID: $assetID) + query AppQuery($assetID: ID!) { + ...AppContainer @arguments(assetID: $assetID) } `} variables={{ - // We cast `null` to any due to restrictions of the current graphql syntax. - assetID: props.local.assetID || (null as any), - // TODO: This is set to false, as server does not support querying assets yet. - showAssetList: !props.local.assetID && false, + assetID: props.local.assetID, }} render={render} /> diff --git a/src/core/client/stream/queries/__snapshots__/AppQuery.spec.tsx.snap b/src/core/client/stream/queries/__snapshots__/AppQuery.spec.tsx.snap new file mode 100644 index 000000000..dbd2ad45a --- /dev/null +++ b/src/core/client/stream/queries/__snapshots__/AppQuery.spec.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders app 1`] = ` + +`; + +exports[`renders error 1`] = ` +
+ error +
+`; + +exports[`renders loading 1`] = ` +
+ Loading +
+`; diff --git a/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap b/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap new file mode 100644 index 000000000..8b37696d2 --- /dev/null +++ b/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap @@ -0,0 +1,210 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`loads more comments 1`] = ` +
+
+

+ Talk NEO +

+
+
+