diff --git a/.eslintrc.js b/.eslintrc.js index f4773f28f2..c7bb16a460 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,7 @@ +'use strict'; + const OFF = 0; -const WARNING = 1; +// const WARNING = 1; const ERROR = 2; module.exports = { @@ -13,7 +15,7 @@ module.exports = { ], ecmaFeatures: { - modules: false + modules: false, }, // We're stricter than the default config, mostly. We'll override a few rules @@ -70,5 +72,9 @@ module.exports = { // CUSTOM RULES // the second argument of warning/invariant should be a literal string 'react-internal/warning-and-invariant-args': ERROR, - } + }, + + globals: { + expectDev: true, + }, }; diff --git a/package.json b/package.json index 76b0861cff..895123ebed 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "gzip-js": "~0.3.2", "jest": "^15.1.1", "jest-config": "^15.1.1", + "jest-jasmine2": "^15.1.1", "jest-runtime": "^15.1.1", "loose-envify": "^1.1.0", "merge-stream": "^1.0.0", diff --git a/scripts/fiber/record-tests b/scripts/fiber/record-tests index 2792f10a08..3b2a83e296 100755 --- a/scripts/fiber/record-tests +++ b/scripts/fiber/record-tests @@ -27,19 +27,51 @@ function wrapRunnerFile(runnerPath) { filename, ` 'use strict'; + var runnerPath = ${JSON.stringify(runnerPath)}; var wrap = require(${JSON.stringify(__filename)}).wrapRunner; - var original = require(${JSON.stringify(runnerPath)}); - module.exports = wrap(original); + module.exports = wrap(runnerPath); ` ); return filename; } -function wrapRunner(original) { - return function runner(config, environment, runtime, testPath) { - return original(config, environment, runtime, testPath) +function wrapRunner(originalPath) { + const original = require(originalPath); + // Assuming originalPath is .../jest-jasmine2/build/index.js + const JasmineReporter = require( + path.join(path.dirname(originalPath), 'reporter.js') + ); + + // For each spec, we store whether there was any expectDev() failure. This + // relies on the results being returned in the same order as they are run. + const hadDevFailures = []; + let environment; + + const oldSpecStarted = JasmineReporter.prototype.specStarted; + JasmineReporter.prototype.specStarted = function(result) { + oldSpecStarted.apply(this, arguments); + + environment.global.__suppressDevFailures = true; + environment.global.__hadDevFailures = false; + }; + + const oldSpecDone = JasmineReporter.prototype.specDone; + JasmineReporter.prototype.specDone = function(result) { + oldSpecDone.apply(this, arguments); + + environment.global.__suppressDevFailures = false; + hadDevFailures.push(environment.global.__hadDevFailures); + }; + + return function runner(config, env, runtime, testPath) { + environment = env; + return original(config, env, runtime, testPath) .then((results) => { results.failureMessage = null; + hadDevFailures.forEach((hadFailures, i) => { + results.testResults[i].hadDevFailures = hadFailures; + }); + hadDevFailures.length = 0; return results; }); }; @@ -91,7 +123,11 @@ function recordTests(trackFacts) { .then((runResults) => { const passing = formatResults( runResults, - (file, test) => test.status === 'passed' + (file, test) => test.status === 'passed' && !test.hadDevFailures + ); + const passingExceptDev = formatResults( + runResults, + (file, test) => test.status === 'passed' && test.hadDevFailures ); const failing = formatResults( runResults, @@ -101,6 +137,10 @@ function recordTests(trackFacts) { path.join(__dirname, 'tests-passing.txt'), passing + '\n' ); + fs.writeFileSync( + path.join(__dirname, 'tests-passing-except-dev.txt'), + passingExceptDev + '\n' + ); fs.writeFileSync( path.join(__dirname, 'tests-failing.txt'), failing + '\n' diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index 5322ee046c..93bd27fea3 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -505,10 +505,6 @@ src/renderers/shared/shared/__tests__/ReactTreeTraversal-test.js * should leave to the window * should leave to the window from the shallowest -src/renderers/shared/stack/reconciler/__tests__/ReactChildReconciler-test.js -* warns for duplicated keys -* warns for duplicated keys with component stack info - src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js * should throw on invalid render targets * should throw when supplying a ref outside of render method diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt new file mode 100644 index 0000000000..54627f9098 --- /dev/null +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -0,0 +1,3 @@ +src/renderers/shared/stack/reconciler/__tests__/ReactChildReconciler-test.js +* warns for duplicated keys +* warns for duplicated keys with component stack info diff --git a/scripts/jest/test-framework-setup.js b/scripts/jest/test-framework-setup.js index e1bb461af1..18543a8248 100644 --- a/scripts/jest/test-framework-setup.js +++ b/scripts/jest/test-framework-setup.js @@ -58,3 +58,26 @@ env.afterEach(() => { expect(console.error).toBeReset(); expect(console.error).toNotHaveBeenCalled(); }); + +function wrapDevMatcher(obj, name) { + const original = obj[name]; + obj[name] = function devMatcher() { + try { + original.apply(this, arguments); + } catch (e) { + global.__hadDevFailures = e.stack; + } + }; +} + +const expectDev = function expectDev(actual) { + const expectation = expect(actual); + if (global.__suppressDevFailures) { + Object.keys(expectation).forEach((name) => { + wrapDevMatcher(expectation, name); + wrapDevMatcher(expectation.not, name); + }); + } + return expectation; +}; +global.expectDev = expectDev; diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactChildReconciler-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactChildReconciler-test.js index 6cc5115a0c..5b1dca7448 100644 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactChildReconciler-test.js +++ b/src/renderers/shared/stack/reconciler/__tests__/ReactChildReconciler-test.js @@ -19,7 +19,7 @@ var ReactTestUtils; describe('ReactChildReconciler', () => { function normalizeCodeLocInfo(str) { - return str.replace(/\(at .+?:\d+\)/g, '(at **)'); + return str && str.replace(/\(at .+?:\d+\)/g, '(at **)'); } beforeEach(() => { @@ -40,8 +40,8 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toContain( 'Child keys must be unique; when two children share a key, only the first child will be used.' ); }); @@ -69,8 +69,8 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expect(console.error.calls.count()).toBe(1); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + expectDev(console.error.calls.count()).toBe(1); + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: flattenChildren(...): ' + 'Encountered two children with the same key, `1`. ' + 'Child keys must be unique; when two children share a key, ' +