diff --git a/.eslintrc b/.eslintrc index 478f849f94..0db2249529 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,5 +8,9 @@ }, "settings": { "version": "detect" + }, + "globals": { + "__DEV__": "readonly", + "jasmine": "readonly" } } diff --git a/flow.js b/flow.js index ee27f03d08..df570e1021 100644 --- a/flow.js +++ b/flow.js @@ -9,3 +9,12 @@ declare module 'events' { declare export default typeof EventEmitter; } + +declare var __DEV__: boolean; + +declare var jasmine: {| + getEnv: () => {| + afterEach: (callback: Function) => void, + beforeEach: (callback: Function) => void, + |}, +|}; diff --git a/package.json b/package.json index 81e52871c2..81c98bb0a7 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,12 @@ "moduleNameMapper": { "^src/(.*)$": "/src/$1" }, + "setupFiles": [ + "/src/__tests__/setupEnv" + ], + "setupFilesAfterEnv": [ + "/src/__tests__/setupTests" + ], "snapshotSerializers": [ "/src/__tests__/storeSerializer" ], diff --git a/src/__tests__/__snapshots__/sanity-test.js.snap b/src/__tests__/__snapshots__/sanity-test.js.snap deleted file mode 100644 index e1a57e551e..0000000000 --- a/src/__tests__/__snapshots__/sanity-test.js.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`sanity should pass 1`] = ` -▾ - ▾ - - - ▾ - - -`; diff --git a/src/__tests__/__snapshots__/store-test.js.snap b/src/__tests__/__snapshots__/store-test.js.snap new file mode 100644 index 0000000000..2787592224 --- /dev/null +++ b/src/__tests__/__snapshots__/store-test.js.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Store should initialize a multiple root tree 1`] = ` +▾ + + +▾ + + +`; + +exports[`Store should initialize a simple tree 1`] = ` +▾ + ▾ + + + ▾ + + +`; diff --git a/src/__tests__/sanity-test.js b/src/__tests__/sanity-test.js deleted file mode 100644 index 80c475ed99..0000000000 --- a/src/__tests__/sanity-test.js +++ /dev/null @@ -1,41 +0,0 @@ -// @flow - -import { setup } from './utils'; - -describe('sanity', () => { - let React; - let ReactDOM; - let store; - - beforeEach(() => { - store = setup(); - - React = require('react'); - ReactDOM = require('react-dom'); - }); - - it('should pass', () => { - function Grandparent() { - return ( - - - - - ); - } - - function Parent() { - return [, ]; - } - - function Child() { - return
Hi!
; - } - - ReactDOM.render(, document.createElement('div')); - - jest.runAllTimers(); // Flush Bridge operations - - expect(store).toMatchSnapshot(); - }); -}); diff --git a/src/__tests__/setupEnv.js b/src/__tests__/setupEnv.js new file mode 100644 index 0000000000..af47699360 --- /dev/null +++ b/src/__tests__/setupEnv.js @@ -0,0 +1,7 @@ +// @flow + +global.localStorage = { + getItem: jest.fn(), + setItem: jest.fn(), + clear: jest.fn(), +}; diff --git a/src/__tests__/setupTests.js b/src/__tests__/setupTests.js new file mode 100644 index 0000000000..762784e7e4 --- /dev/null +++ b/src/__tests__/setupTests.js @@ -0,0 +1,50 @@ +// @flow + +import Agent from 'src/backend/agent'; +import { initBackend } from 'src/backend'; +import Bridge from 'src/bridge'; +import Store from 'src/devtools/store'; +import { installHook } from 'src/hook'; + +let agent; +let bridge; +let bridgeListeners; + +// Mimic the global we set with Webpack's DefinePlugin +global.__DEV__ = process.env.NODE_ENV !== 'production'; + +const env = jasmine.getEnv(); +env.beforeEach(() => { + // It's important to reset modules between test runs; + // Without this, ReactDOM won't re-inject itself into the new hook. + jest.resetModules(); + + // Fake timers let us flush Bridge operations between setup and assertions. + jest.useFakeTimers(); + + installHook(global); + + bridgeListeners = []; + + bridge = new Bridge( + { + listen(callback) { + bridgeListeners.push(callback); + }, + send(event: string, payload: any, transferable?: Array) { + bridgeListeners.forEach(callback => callback({ event, payload })); + }, + }, + { batchDuration: 0 } + ); + + agent = new Agent(); + agent.addBridge(bridge); + + initBackend(global.__REACT_DEVTOOLS_GLOBAL_HOOK__, agent, global); + + global.store = new Store(bridge); +}); +env.afterEach(() => { + delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__; +}); diff --git a/src/__tests__/store-test.js b/src/__tests__/store-test.js new file mode 100644 index 0000000000..1b564a03cb --- /dev/null +++ b/src/__tests__/store-test.js @@ -0,0 +1,43 @@ +// @flow + +describe('Store', () => { + let React; + let ReactDOM; + let store; + + beforeEach(() => { + store = global.store; + + React = require('react'); + ReactDOM = require('react-dom'); + }); + + it('should initialize a simple tree', () => { + const Grandparent = () => ( + + + + + ); + const Parent = () => [, ]; + const Child = () =>
Hi!
; + + ReactDOM.render(, document.createElement('div')); + + jest.runAllTimers(); // Flush Bridge operations + + expect(store).toMatchSnapshot(); + }); + + it('should initialize a multiple root tree', () => { + const Parent = () => [, ]; + const Child = () =>
Hi!
; + + ReactDOM.render(, document.createElement('div')); + ReactDOM.render(, document.createElement('div')); + + jest.runAllTimers(); // Flush Bridge operations + + expect(store).toMatchSnapshot(); + }); +}); diff --git a/src/__tests__/utils.js b/src/__tests__/utils.js deleted file mode 100644 index d9ef2b78f0..0000000000 --- a/src/__tests__/utils.js +++ /dev/null @@ -1,40 +0,0 @@ -// @flow - -import Agent from 'src/backend/agent'; -import { initBackend } from 'src/backend'; -import Bridge from 'src/bridge'; -import Store from 'src/devtools/store'; -import { installHook } from 'src/hook'; - -export function setup(): Store { - installHook(global); - - jest.useFakeTimers(); - - // TODO Replace this with a polyfill? - global.localStorage = { - getItem: jest.fn(), - setItem: jest.fn(), - clear: jest.fn(), - }; - - const listeners = []; - const bridge = new Bridge( - { - listen(callback) { - listeners.push(callback); - }, - send(event: string, payload: any, transferable?: Array) { - listeners.forEach(callback => callback({ event, payload })); - }, - }, - { batchDuration: 0 } - ); - - const agent = new Agent(); - agent.addBridge(bridge); - - initBackend(global.__REACT_DEVTOOLS_GLOBAL_HOOK__, agent, global); - - return new Store(bridge); -} diff --git a/src/hook.js b/src/hook.js index fa960d73be..23f7280839 100644 --- a/src/hook.js +++ b/src/hook.js @@ -189,6 +189,9 @@ export function installHook(target: any): DevToolsHook | null { target, '__REACT_DEVTOOLS_GLOBAL_HOOK__', ({ + // This property needs to be configurable for the test environment, + // else we won't be able to delete and recreate it beween tests. + configurable: __DEV__, enumerable: false, get() { return hook;