diff --git a/.eslintrc.json b/.eslintrc.json index e0c6aec..b7597ab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,11 +9,24 @@ "import/no-mutable-exports": "off", "no-nested-ternary": "off", "no-param-reassign": "off", - "func-names": "off" + "func-names": "off", + "max-classes-per-file": "off", + "react/prefer-stateless-function": "off", + "react/destructuring-assignment": "off", + "react/state-in-constructor": "off", + "react/prop-types": "off" }, "globals": { "window": true, "EventTarget": true, - "WebSocket": true + "WebSocket": true, + "describe": true, + "afterAll": true, + "test": true, + "expect": true, + "afterEach": true, + "document": true, + "jest": true, + "Event": true } } diff --git a/__mocks__/react-dom.js b/__mocks__/react-dom.js deleted file mode 100644 index 2251e01..0000000 --- a/__mocks__/react-dom.js +++ /dev/null @@ -1,3 +0,0 @@ -// this is here to avoid duplicate react entries in the examples -// (one from the example's node_modules and one from the root's node_modules) -module.exports = require('react-dom') diff --git a/__mocks__/react-easy-state.js b/__mocks__/react-easy-state.js index ef63051..882d144 100644 --- a/__mocks__/react-easy-state.js +++ b/__mocks__/react-easy-state.js @@ -1,8 +1,9 @@ -const path = require('path') +const path = require('path'); -const bundleName = process.env.BUNDLE +const bundleName = process.env.BUNDLE; const bundlePath = path.resolve( - bundleName ? `dist/${bundleName}` : 'src/index.js' -) + bundleName ? `dist/${bundleName}` : 'src/index.js', +); -module.exports = require(bundlePath) +// eslint-disable-next-line import/no-dynamic-require +module.exports = require(bundlePath); diff --git a/__mocks__/react-native.js b/__mocks__/react-native.js index 56f6104..1230fb3 100644 --- a/__mocks__/react-native.js +++ b/__mocks__/react-native.js @@ -1,3 +1,3 @@ // this is here to avoid duplicate react entries in the examples // (one from the example's node_modules and one from the root's node_modules) -module.exports = require('react-native') +module.exports = require('react-native'); diff --git a/__mocks__/react-platform.js b/__mocks__/react-platform.js deleted file mode 100644 index 5909066..0000000 --- a/__mocks__/react-platform.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = process.env.NATIVE - ? require('react-native') - : require('react-dom') diff --git a/__mocks__/react.js b/__mocks__/react.js index 1ee9232..5ff273d 100644 --- a/__mocks__/react.js +++ b/__mocks__/react.js @@ -1,3 +1,3 @@ // this is here to avoid duplicate react entries in the examples // (one from the example's node_modules and one from the root's node_modules) -module.exports = require('react') +module.exports = require('react'); diff --git a/__tests__/Clock.test.js b/__tests__/Clock.test.js deleted file mode 100644 index febce7d..0000000 --- a/__tests__/Clock.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import React, { StrictMode } from 'react' -import { render, cleanup, act } from '@testing-library/react/pure' -import sinon from 'sinon' -import App from '../examples/clock/src/App' - -describe('Clock App', () => { - const clock = sinon.useFakeTimers() - const { container, unmount } = render( - - - - ) - - const clearIntervalSpy = sinon.spy(global, 'clearInterval') - - afterAll(() => { - cleanup() - clock.restore() - clearIntervalSpy.restore() - }) - - test('should update to display the current time every second', () => { - expect(container).toHaveTextContent('12:00:00 AM') - - act(() => { - clock.tick(2000) - }) - expect(container).toHaveTextContent('12:00:02 AM') - - act(() => { - clock.tick(8500) - }) - expect(container).toHaveTextContent('12:00:10 AM') - }) - - test('should clean up the interval timer when the component is unmounted', () => { - unmount() - expect(clearIntervalSpy.callCount).toBe(1) - }) -}) diff --git a/__tests__/Clock.test.jsx b/__tests__/Clock.test.jsx new file mode 100644 index 0000000..fbd68b3 --- /dev/null +++ b/__tests__/Clock.test.jsx @@ -0,0 +1,40 @@ +import React, { StrictMode } from 'react'; +import { render, cleanup, act } from '@testing-library/react/pure'; +import sinon from 'sinon'; +import App from '../examples/clock/src/App'; + +describe('Clock App', () => { + const clock = sinon.useFakeTimers(); + const { container, unmount } = render( + + + , + ); + + const clearIntervalSpy = sinon.spy(global, 'clearInterval'); + + afterAll(() => { + cleanup(); + clock.restore(); + clearIntervalSpy.restore(); + }); + + test('should update to display the current time every second', () => { + expect(container).toHaveTextContent('12:00:00 AM'); + + act(() => { + clock.tick(2000); + }); + expect(container).toHaveTextContent('12:00:02 AM'); + + act(() => { + clock.tick(8500); + }); + expect(container).toHaveTextContent('12:00:10 AM'); + }); + + test('should clean up the interval timer when the component is unmounted', () => { + unmount(); + expect(clearIntervalSpy.callCount).toBe(1); + }); +}); diff --git a/__tests__/Clock.test.native.js b/__tests__/Clock.test.native.js deleted file mode 100644 index c57ef6d..0000000 --- a/__tests__/Clock.test.native.js +++ /dev/null @@ -1,37 +0,0 @@ -import React, { StrictMode } from 'react' -import { render, flushMicrotasksQueue } from 'react-native-testing-library' -import sinon from 'sinon' -import App from '../examples/native-clock/App' - -describe('Clock App', () => { - const clock = sinon.useFakeTimers() - const { getByText, unmount } = render( - - - - ) - // flush the inital didMount effect - flushMicrotasksQueue() - - const clearIntervalSpy = sinon.spy(global, 'clearInterval') - - afterAll(() => { - clock.restore() - clearIntervalSpy.restore() - }) - - test('should update to display the current time every second', () => { - expect(getByText('12:00:00 AM')).toBeDefined() - - clock.tick(2000) - expect(getByText('12:00:02 AM')).toBeDefined() - - clock.tick(8500) - expect(getByText('12:00:10 AM')).toBeDefined() - }) - - test('should clean up the interval timer when the component is unmounted', () => { - unmount() - expect(clearIntervalSpy.callCount).toBe(1) - }) -}) diff --git a/__tests__/Clock.test.native.jsx b/__tests__/Clock.test.native.jsx new file mode 100644 index 0000000..8436f97 --- /dev/null +++ b/__tests__/Clock.test.native.jsx @@ -0,0 +1,40 @@ +import React, { StrictMode } from 'react'; +import { + render, + flushMicrotasksQueue, +} from 'react-native-testing-library'; +import sinon from 'sinon'; +import App from '../examples/native-clock/App'; + +describe('Clock App', () => { + const clock = sinon.useFakeTimers(); + const { getByText, unmount } = render( + + + , + ); + // flush the inital didMount effect + flushMicrotasksQueue(); + + const clearIntervalSpy = sinon.spy(global, 'clearInterval'); + + afterAll(() => { + clock.restore(); + clearIntervalSpy.restore(); + }); + + test('should update to display the current time every second', () => { + expect(getByText('12:00:00 AM')).toBeDefined(); + + clock.tick(2000); + expect(getByText('12:00:02 AM')).toBeDefined(); + + clock.tick(8500); + expect(getByText('12:00:10 AM')).toBeDefined(); + }); + + test('should clean up the interval timer when the component is unmounted', () => { + unmount(); + expect(clearIntervalSpy.callCount).toBe(1); + }); +}); diff --git a/__tests__/Contacts.test.js b/__tests__/Contacts.test.js deleted file mode 100644 index b1270d3..0000000 --- a/__tests__/Contacts.test.js +++ /dev/null @@ -1,96 +0,0 @@ -import React, { StrictMode } from 'react' -import { render, cleanup, fireEvent } from '@testing-library/react/pure' -import App from '../examples/contacts/src/App' - -describe('Contacts App', () => { - const { container } = render( - - - - ) - afterAll(cleanup) - - test('should add new contacts', () => { - expect(container).toMatchSnapshot('01. Initial state') - - const nameField = container.querySelector('input[name="name"]') - const emailField = container.querySelector('input[name="email"]') - const createButton = container.querySelector('button') - - fireEvent.change(nameField, { - target: { name: 'name', value: 'Test Contact' } - }) - expect(container).toMatchSnapshot('02. Create Test Contact name') - - fireEvent.change(emailField, { - target: { name: 'email', value: 'test.contact@gmail.com' } - }) - expect(container).toMatchSnapshot('03. Create Test Contact email') - - fireEvent.click(createButton) - expect(container).toMatchSnapshot('04. Add Test Contact') - - fireEvent.change(nameField, { - target: { name: 'name', value: '' } - }) - fireEvent.change(emailField, { - target: { name: 'email', value: '' } - }) - fireEvent.click(createButton) - expect(container).toMatchSnapshot('05. Add Placeholder Contact') - }) - - test('should edit contact', () => { - let display, editor, editButton - - display = container.querySelector('.contact-display') - editButton = display.querySelector('.zmdi-edit') - - fireEvent.click(editButton) - expect(container).toMatchSnapshot('06. Switch Test Contact to Edit Mode') - - editor = container.querySelector('.contact-editor') - const nameField = editor.querySelector('input[name="name"]') - const cancelButton = editor.querySelector('.zmdi-close') - - fireEvent.change(nameField, { - target: { name: 'name', value: 'Edited Test Contact' } - }) - expect(container).toMatchSnapshot('07. Edit Test Contact name') - - fireEvent.click(cancelButton) - expect(container).toMatchSnapshot('08. Cancel Test Contact edit') - - display = container.querySelector('.contact-display') - editButton = display.querySelector('.zmdi-edit') - - fireEvent.click(editButton) - expect(container).toMatchSnapshot('09. Switch Test Contact to edit Mode') - - editor = container.querySelector('.contact-editor') - const emailField = editor.querySelector('input[name="email"]') - const saveButton = editor.querySelector('.zmdi-save') - - fireEvent.change(emailField, { - target: { name: 'email', value: 'test.contact.edited@gmail.com' } - }) - expect(container).toMatchSnapshot('10. Edit Test Contact email') - - fireEvent.click(saveButton) - expect(container).toMatchSnapshot('11. Save Test Contact edit') - }) - - test('should delete contact', () => { - let deleteButton = container.querySelectorAll( - '.contact-display .zmdi-delete' - )[1] - - fireEvent.click(deleteButton) - expect(container).toMatchSnapshot('12. Delete Placeholder Contact') - - deleteButton = container.querySelector('.contact-display .zmdi-delete') - - fireEvent.click(deleteButton) - expect(container).toMatchSnapshot('13. Delete Test Contact') - }) -}) diff --git a/__tests__/Contacts.test.jsx b/__tests__/Contacts.test.jsx new file mode 100644 index 0000000..babd7bf --- /dev/null +++ b/__tests__/Contacts.test.jsx @@ -0,0 +1,115 @@ +import React, { StrictMode } from 'react'; +import { + render, + cleanup, + fireEvent, +} from '@testing-library/react/pure'; +import App from '../examples/contacts/src/App'; + +describe('Contacts App', () => { + const { container } = render( + + + , + ); + afterAll(cleanup); + + test('should add new contacts', () => { + expect(container).toMatchSnapshot('01. Initial state'); + + const nameField = container.querySelector('input[name="name"]'); + const emailField = container.querySelector('input[name="email"]'); + const createButton = container.querySelector('button'); + + fireEvent.change(nameField, { + target: { name: 'name', value: 'Test Contact' }, + }); + expect(container).toMatchSnapshot('02. Create Test Contact name'); + + fireEvent.change(emailField, { + target: { name: 'email', value: 'test.contact@gmail.com' }, + }); + expect(container).toMatchSnapshot( + '03. Create Test Contact email', + ); + + fireEvent.click(createButton); + expect(container).toMatchSnapshot('04. Add Test Contact'); + + fireEvent.change(nameField, { + target: { name: 'name', value: '' }, + }); + fireEvent.change(emailField, { + target: { name: 'email', value: '' }, + }); + fireEvent.click(createButton); + expect(container).toMatchSnapshot('05. Add Placeholder Contact'); + }); + + test('should edit contact', () => { + let display; + let editor; + let editButton; + + display = container.querySelector('.contact-display'); + editButton = display.querySelector('.zmdi-edit'); + + fireEvent.click(editButton); + expect(container).toMatchSnapshot( + '06. Switch Test Contact to Edit Mode', + ); + + editor = container.querySelector('.contact-editor'); + const nameField = editor.querySelector('input[name="name"]'); + const cancelButton = editor.querySelector('.zmdi-close'); + + fireEvent.change(nameField, { + target: { name: 'name', value: 'Edited Test Contact' }, + }); + expect(container).toMatchSnapshot('07. Edit Test Contact name'); + + fireEvent.click(cancelButton); + expect(container).toMatchSnapshot('08. Cancel Test Contact edit'); + + display = container.querySelector('.contact-display'); + editButton = display.querySelector('.zmdi-edit'); + + fireEvent.click(editButton); + expect(container).toMatchSnapshot( + '09. Switch Test Contact to edit Mode', + ); + + editor = container.querySelector('.contact-editor'); + const emailField = editor.querySelector('input[name="email"]'); + const saveButton = editor.querySelector('.zmdi-save'); + + fireEvent.change(emailField, { + target: { + name: 'email', + value: 'test.contact.edited@gmail.com', + }, + }); + expect(container).toMatchSnapshot('10. Edit Test Contact email'); + + fireEvent.click(saveButton); + expect(container).toMatchSnapshot('11. Save Test Contact edit'); + }); + + test('should delete contact', () => { + let deleteButton = container.querySelectorAll( + '.contact-display .zmdi-delete', + )[1]; + + fireEvent.click(deleteButton); + expect(container).toMatchSnapshot( + '12. Delete Placeholder Contact', + ); + + deleteButton = container.querySelector( + '.contact-display .zmdi-delete', + ); + + fireEvent.click(deleteButton); + expect(container).toMatchSnapshot('13. Delete Test Contact'); + }); +}); diff --git a/__tests__/TodoMVC.test.js b/__tests__/TodoMVC.test.js deleted file mode 100644 index f0db1a6..0000000 --- a/__tests__/TodoMVC.test.js +++ /dev/null @@ -1,94 +0,0 @@ -import React, { StrictMode } from 'react' -import { render, cleanup, fireEvent } from '@testing-library/react/pure' -import App from '../examples/todo-mvc/src/App' - -describe('TodoMVC App', () => { - const { container } = render( - - - - ) - afterAll(cleanup) - - test('should add todos', () => { - expect(container).toMatchSnapshot('01. Initial state') - - const input = container.querySelector('.new-todo') - - fireEvent.keyUp(input, { - keyCode: 13, - target: { value: 'Test Todo' } - }) - expect(container).toMatchSnapshot('02. Add Test Todo') - - fireEvent.keyUp(input, { - keyCode: 13, - target: { value: 'Other Todo' } - }) - expect(container).toMatchSnapshot('03. Add Other Todo') - - fireEvent.keyUp(input, { - keyCode: 27, - target: { value: 'Final Tod' } - }) - fireEvent.keyUp(input, { - keyCode: 13, - target: { value: 'Final Todo' } - }) - expect(container).toMatchSnapshot('04. Add Final Todo') - }) - - test('should toggle todo status', () => { - const toggles = container.querySelectorAll('.todo-list .toggle') - - fireEvent.click(toggles[0]) - expect(container).toMatchSnapshot('05. Toggle Test Todo to completed') - - fireEvent.click(toggles[1]) - expect(container).toMatchSnapshot('06. Toggle Other Todo to completed') - - fireEvent.click(toggles[0]) - expect(container).toMatchSnapshot('07. Toggle Test Todo to active') - }) - - test('should filter todos', () => { - const completedFilter = container.querySelector( - 'button[value="completed"]' - ) - const activeFilter = container.querySelector('button[value="active"]') - const allFilter = container.querySelector('button[value="all"]') - - fireEvent.click(completedFilter) - expect(container).toMatchSnapshot('08. Filter completed') - - fireEvent.click(activeFilter) - expect(container).toMatchSnapshot('09. Filter active') - - fireEvent.click(allFilter) - expect(container).toMatchSnapshot('10. Filter all') - }) - - test('should clear completed', () => { - const clearCompleted = container.querySelector('.clear-completed') - - fireEvent.click(clearCompleted) - expect(container).toMatchSnapshot('11. Clear completed') - }) - - test('should toggle all todo state at once', () => { - const toggleAll = container.querySelector('.toggle-all') - - fireEvent.click(toggleAll) - expect(container).toMatchSnapshot('12. Toggle all to completed') - - fireEvent.click(toggleAll) - expect(container).toMatchSnapshot('13. Toggle all to active') - }) - - test('should delete todo', () => { - const deleter = container.querySelector('.todo-list .destroy') - - fireEvent.click(deleter) - expect(container).toMatchSnapshot('14. Delete Test Todo') - }) -}) diff --git a/__tests__/TodoMVC.test.jsx b/__tests__/TodoMVC.test.jsx new file mode 100644 index 0000000..7c1b836 --- /dev/null +++ b/__tests__/TodoMVC.test.jsx @@ -0,0 +1,108 @@ +import React, { StrictMode } from 'react'; +import { + render, + cleanup, + fireEvent, +} from '@testing-library/react/pure'; +import App from '../examples/todo-mvc/src/App'; + +describe('TodoMVC App', () => { + const { container } = render( + + + , + ); + afterAll(cleanup); + + test('should add todos', () => { + expect(container).toMatchSnapshot('01. Initial state'); + + const input = container.querySelector('.new-todo'); + + fireEvent.keyUp(input, { + keyCode: 13, + target: { value: 'Test Todo' }, + }); + expect(container).toMatchSnapshot('02. Add Test Todo'); + + fireEvent.keyUp(input, { + keyCode: 13, + target: { value: 'Other Todo' }, + }); + expect(container).toMatchSnapshot('03. Add Other Todo'); + + fireEvent.keyUp(input, { + keyCode: 27, + target: { value: 'Final Tod' }, + }); + fireEvent.keyUp(input, { + keyCode: 13, + target: { value: 'Final Todo' }, + }); + expect(container).toMatchSnapshot('04. Add Final Todo'); + }); + + test('should toggle todo status', () => { + const toggles = container.querySelectorAll('.todo-list .toggle'); + + fireEvent.click(toggles[0]); + expect(container).toMatchSnapshot( + '05. Toggle Test Todo to completed', + ); + + fireEvent.click(toggles[1]); + expect(container).toMatchSnapshot( + '06. Toggle Other Todo to completed', + ); + + fireEvent.click(toggles[0]); + expect(container).toMatchSnapshot( + '07. Toggle Test Todo to active', + ); + }); + + test('should filter todos', () => { + const completedFilter = container.querySelector( + 'button[value="completed"]', + ); + const activeFilter = container.querySelector( + 'button[value="active"]', + ); + const allFilter = container.querySelector('button[value="all"]'); + + fireEvent.click(completedFilter); + expect(container).toMatchSnapshot('08. Filter completed'); + + fireEvent.click(activeFilter); + expect(container).toMatchSnapshot('09. Filter active'); + + fireEvent.click(allFilter); + expect(container).toMatchSnapshot('10. Filter all'); + }); + + test('should clear completed', () => { + const clearCompleted = container.querySelector( + '.clear-completed', + ); + + fireEvent.click(clearCompleted); + expect(container).toMatchSnapshot('11. Clear completed'); + }); + + test('should toggle all todo state at once', () => { + const toggleAll = container.querySelector('.toggle-all'); + + fireEvent.click(toggleAll); + expect(container).toMatchSnapshot('12. Toggle all to completed'); + + fireEvent.click(toggleAll); + expect(container).toMatchSnapshot('13. Toggle all to active'); + }); + + test('should delete todo', () => { + const deleter = container.querySelector('.todo-list .destroy'); + + fireEvent.click(deleter); + expect(container).toMatchSnapshot('14. Delete Test Todo'); + }); +}); diff --git a/__tests__/__snapshots__/Contacts.test.js.snap b/__tests__/__snapshots__/Contacts.test.jsx.snap similarity index 100% rename from __tests__/__snapshots__/Contacts.test.js.snap rename to __tests__/__snapshots__/Contacts.test.jsx.snap diff --git a/__tests__/__snapshots__/TodoMVC.test.js.snap b/__tests__/__snapshots__/TodoMVC.test.jsx.snap similarity index 100% rename from __tests__/__snapshots__/TodoMVC.test.js.snap rename to __tests__/__snapshots__/TodoMVC.test.jsx.snap diff --git a/__tests__/batching.test.js b/__tests__/batching.test.jsx similarity index 96% rename from __tests__/batching.test.js rename to __tests__/batching.test.jsx index b77ce86..9ccbc98 100644 --- a/__tests__/batching.test.js +++ b/__tests__/batching.test.jsx @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import { render, cleanup, act } from '@testing-library/react/pure'; +// eslint-disable-next-line import/no-unresolved import { view, store, batch } from 'react-easy-state'; describe('batching', () => { @@ -9,7 +10,7 @@ describe('batching', () => { let renderCount = 0; const person = store({ name: 'Bob' }); const MyComp = view(() => { - renderCount++; + renderCount += 1; return
{person.name}
; }); @@ -32,7 +33,7 @@ describe('batching', () => { const MyComp = view( class extends Component { render() { - renderCount++; + renderCount += 1; return
{person.name}
; } }, @@ -53,7 +54,7 @@ describe('batching', () => { let renderCount = 0; const person = store({ name: 'Bob' }); const MyComp = view(() => { - renderCount++; + renderCount += 1; return
{person.name}
; }); @@ -74,7 +75,7 @@ describe('batching', () => { let renderCount = 0; const person = store({ name: 'Bob' }); const MyComp = view(() => { - renderCount++; + renderCount += 1; return
{person.name}
; }); @@ -100,7 +101,7 @@ describe('batching', () => { let renderCount = 0; const person = store({ name: 'Bob' }); const MyComp = view(() => { - renderCount++; + renderCount += 1; return
{person.name}
; }); @@ -128,7 +129,7 @@ describe('batching', () => { let renderCount = 0; const person = store({ name: 'Bob' }); const MyComp = view(() => { - renderCount++; + renderCount += 1; return
{person.name}
; }); @@ -152,7 +153,7 @@ describe('batching', () => { let renderCount = 0; const person = store({ name: 'Bob' }); const MyComp = view(() => { - renderCount++; + renderCount += 1; return
{person.name}
; }); @@ -179,7 +180,7 @@ describe('batching', () => { let renderCount = 0; const person = store({ name: 'Bob' }); const MyComp = view(() => { - renderCount++; + renderCount += 1; return
{person.name}
; }); @@ -205,7 +206,7 @@ describe('batching', () => { let renderCount = 0; const person = store({ name: 'Bob' }); const MyComp = view(() => { - renderCount++; + renderCount += 1; return
{person.name}
; }); @@ -235,7 +236,7 @@ describe('batching', () => { let renderCount = 0; const person = store({ name: 'Bob' }); const MyComp = view(() => { - renderCount++; + renderCount += 1; return
{person.name}
; }); @@ -281,7 +282,9 @@ describe('batching', () => { test('should not break event listeners', () => { let callCount = 0; - const fn = () => callCount++; + const fn = () => { + callCount += 1; + }; document.body.addEventListener('click', fn); expect(callCount).toBe(0); diff --git a/__tests__/edgeCases.test.js b/__tests__/edgeCases.test.js deleted file mode 100644 index ade77eb..0000000 --- a/__tests__/edgeCases.test.js +++ /dev/null @@ -1,236 +0,0 @@ -import React, { Component, useState } from 'react' -import { render, cleanup, fireEvent, act } from '@testing-library/react/pure' -import { view, store, batch } from 'react-easy-state' - -describe('edge cases', () => { - afterEach(cleanup) - - test('view() should respect shouldComponentUpdate', () => { - const person = store({ name: 'Bob' }) - const MyComp = view( - class extends Component { - shouldComponentUpdate () { - return false - } - - render () { - return
{person.name}
- } - } - ) - - const { container } = render() - expect(container).toHaveTextContent('Bob') - person.name = 'Ann' - expect(container).toHaveTextContent('Bob') - }) - - test('view() should respect componentWillUnmount', () => { - let didUnMount = false - - const MyComp = view( - class extends Component { - componentWillUnmount () { - didUnMount = true - } - - render () { - return
Hello
- } - } - ) - - const { unmount } = render() - expect(didUnMount).toBe(false) - unmount() - expect(didUnMount).toBe(true) - }) - - test('should not change vanilla setState behavior', () => { - const MyComp = view( - class extends Component { - state = { counter: 0 }; - handleIncrement = () => - this.setState({ counter: this.state.counter + 1 }); - - render () { - return
{this.state.counter}
- } - } - ) - - const { container } = render() - expect(container).toHaveTextContent('0') - fireEvent.click(container.querySelector('div')) - expect(container).toHaveTextContent('1') - }) - - test("should not render when state or props don't change", () => { - const MyComp = view( - class extends Component { - state = { counter: 0 }; - handleIncrement = () => - this.setState({ counter: this.state.counter + 1 }); - - render () { - return ( -
- - -
- ) - } - } - ) - - const RawChild = jest.fn().mockReturnValue(

Test

) - const Child = view(RawChild) - - const { container } = render() - expect(RawChild.mock.calls.length).toBe(1) - fireEvent.click(container.querySelector('button')) - expect(RawChild.mock.calls.length).toBe(1) - }) - - test('view() should respect custom deriveStoresFromProps', () => { - const MyComp = view( - class extends Component { - store1 = store({ num: 0 }); - store2 = store({ num: 1 }); - - static deriveStoresFromProps (props, store1, store2) { - store1.num = props.num1 || store1.num - store2.num = props.num2 || store2.num - } - - handleOnClick = () => this.store1.num++; - - render () { - return ( -
- {this.store1.num} - {this.store2.num} -
- ) - } - } - ) - - const { container } = render() - expect(container).toHaveTextContent('11') - fireEvent.click(container.querySelector('div')) - expect(container).toHaveTextContent('11') - }) - - test('view() should respect getDerivedStateFromProps', () => { - const MyComp = view( - class extends Component { - state = { num: 2 }; - static getDerivedStateFromProps (props, state) { - return { - num: props.num || state.num - } - } - - render () { - return
{this.state.num}
- } - } - ) - - const { container } = render() - expect(container).toHaveTextContent('2') - }) - - test('view() should work with other hooks', () => { - const MyComp = view(() => { - const [num, setNum] = useState(0) - return - }) - - const { container } = render() - expect(container).toHaveTextContent('0') - fireEvent.click(container.querySelector('button')) - expect(container).toHaveTextContent('1') - }) - - describe('reactive renders should run in parent - child order with no duplicate child runs from props', () => { - test('should work with function components', () => { - const appStore = store({ num: 1, nested: { num: 12 } }) - - function change () { - appStore.num = 0 - appStore.nested = undefined - } - - let parentCalls = 0 - let childCalls = 0 - - const Child = view(function Child () { - childCalls++ - return ( -
- {appStore.nested.num}, {appStore.num} -
- ) - }) - - const Parent = view(function Parent () { - parentCalls++ - return appStore.nested ? : null - }) - - const { container } = render() - expect(container).toHaveTextContent('12, 1') - expect(parentCalls).toBe(1) - expect(childCalls).toBe(1) - act(() => batch(change)) - expect(container).toHaveTextContent('') - expect(parentCalls).toBe(2) - expect(childCalls).toBe(1) - }) - - test('should work with class components', () => { - const appStore = store({ num: 1, nested: { num: 12 } }) - - function change () { - appStore.num = 0 - appStore.nested = undefined - } - - let parentCalls = 0 - let childCalls = 0 - - const Child = view( - class Child extends Component { - render () { - childCalls++ - return ( -
- {appStore.nested.num}, {appStore.num} -
- ) - } - } - ) - - const Parent = view( - class Parent extends Component { - render () { - parentCalls++ - return appStore.nested ? : null - } - } - ) - - const { container } = render() - expect(container).toHaveTextContent('12, 1') - expect(parentCalls).toBe(1) - expect(childCalls).toBe(1) - act(() => batch(change)) - expect(container).toHaveTextContent('') - expect(parentCalls).toBe(2) - expect(childCalls).toBe(1) - }) - }) -}) diff --git a/__tests__/edgeCases.test.jsx b/__tests__/edgeCases.test.jsx new file mode 100644 index 0000000..cf58c75 --- /dev/null +++ b/__tests__/edgeCases.test.jsx @@ -0,0 +1,259 @@ +import React, { Component, useState } from 'react'; +import { + render, + cleanup, + fireEvent, + act, +} from '@testing-library/react/pure'; +// eslint-disable-next-line import/no-unresolved +import { view, store, batch } from 'react-easy-state'; + +describe('edge cases', () => { + afterEach(cleanup); + + test('view() should respect shouldComponentUpdate', () => { + const person = store({ name: 'Bob' }); + const MyComp = view( + class extends Component { + shouldComponentUpdate() { + return false; + } + + render() { + return
{person.name}
; + } + }, + ); + + const { container } = render(); + expect(container).toHaveTextContent('Bob'); + person.name = 'Ann'; + expect(container).toHaveTextContent('Bob'); + }); + + test('view() should respect componentWillUnmount', () => { + let didUnMount = false; + + const MyComp = view( + class extends Component { + componentWillUnmount() { + didUnMount = true; + } + + render() { + return
Hello
; + } + }, + ); + + const { unmount } = render(); + expect(didUnMount).toBe(false); + unmount(); + expect(didUnMount).toBe(true); + }); + + test('should not change vanilla setState behavior', () => { + const MyComp = view( + class extends Component { + state = { counter: 0 }; + + handleIncrement = () => { + this.setState(prevState => ({ + counter: prevState.counter + 1, + })); + }; + + render() { + return ( + + ); + } + }, + ); + + const { container } = render(); + expect(container).toHaveTextContent('0'); + fireEvent.click(container.querySelector('button')); + expect(container).toHaveTextContent('1'); + }); + + test("should not render when state or props don't change", () => { + const MyComp = view( + class extends Component { + state = { counter: 0 }; + + handleIncrement = () => { + this.setState(prevState => ({ + counter: prevState.counter + 1, + })); + }; + + render() { + return ( +
+ + +
+ ); + } + }, + ); + + const RawChild = jest.fn().mockReturnValue(

Test

); + const Child = view(RawChild); + + const { container } = render(); + expect(RawChild.mock.calls.length).toBe(1); + fireEvent.click(container.querySelector('button')); + expect(RawChild.mock.calls.length).toBe(1); + }); + + test('view() should respect custom deriveStoresFromProps', () => { + const MyComp = view( + class extends Component { + // eslint-disable-next-line react/sort-comp + store1 = store({ num: 0 }); + + store2 = store({ num: 1 }); + + static deriveStoresFromProps(props, store1, store2) { + store1.num = props.num1 || store1.num; + store2.num = props.num2 || store2.num; + } + + handleOnClick = () => { + this.store1.num += 1; + }; + + render() { + return ( + + ); + } + }, + ); + + const { container } = render(); + expect(container).toHaveTextContent('11'); + fireEvent.click(container.querySelector('button')); + expect(container).toHaveTextContent('11'); + }); + + test('view() should respect getDerivedStateFromProps', () => { + const MyComp = view( + class extends Component { + state = { num: 2 }; + + static getDerivedStateFromProps(props, state) { + return { + num: props.num || state.num, + }; + } + + render() { + return
{this.state.num}
; + } + }, + ); + + const { container } = render(); + expect(container).toHaveTextContent('2'); + }); + + test('view() should work with other hooks', () => { + const MyComp = view(() => { + const [num, setNum] = useState(0); + return ( + + ); + }); + + const { container } = render(); + expect(container).toHaveTextContent('0'); + fireEvent.click(container.querySelector('button')); + expect(container).toHaveTextContent('1'); + }); + + describe('reactive renders should run in parent - child order with no duplicate child runs from props', () => { + test('should work with function components', () => { + const appStore = store({ num: 1, nested: { num: 12 } }); + + function change() { + appStore.num = 0; + appStore.nested = undefined; + } + + let parentCalls = 0; + let childCalls = 0; + + const Child = view(function Child() { + childCalls += 1; + return
{`${appStore.nested.num}, ${appStore.num}`}
; + }); + + const Parent = view(function Parent() { + parentCalls += 1; + return appStore.nested ? : null; + }); + + const { container } = render(); + expect(container).toHaveTextContent('12, 1'); + expect(parentCalls).toBe(1); + expect(childCalls).toBe(1); + act(() => batch(change)); + expect(container).toHaveTextContent(''); + expect(parentCalls).toBe(2); + expect(childCalls).toBe(1); + }); + + test('should work with class components', () => { + const appStore = store({ num: 1, nested: { num: 12 } }); + + function change() { + appStore.num = 0; + appStore.nested = undefined; + } + + let parentCalls = 0; + let childCalls = 0; + + const Child = view( + class Child extends Component { + render() { + childCalls += 1; + return ( +
{`${appStore.nested.num}, ${appStore.num}`}
+ ); + } + }, + ); + + const Parent = view( + class Parent extends Component { + render() { + parentCalls += 1; + return appStore.nested ? : null; + } + }, + ); + + const { container } = render(); + expect(container).toHaveTextContent('12, 1'); + expect(parentCalls).toBe(1); + expect(childCalls).toBe(1); + act(() => batch(change)); + expect(container).toHaveTextContent(''); + expect(parentCalls).toBe(2); + expect(childCalls).toBe(1); + }); + }); +}); diff --git a/__tests__/router.test.js b/__tests__/router.test.js deleted file mode 100644 index 6d39948..0000000 --- a/__tests__/router.test.js +++ /dev/null @@ -1,108 +0,0 @@ -import React, { Component } from 'react' -import { render, cleanup, fireEvent, act } from '@testing-library/react/pure' -import { view, store } from 'react-easy-state' -import { - BrowserRouter as Router, - Route, - Link, - withRouter -} from 'react-router-dom' - -describe('withRouter interaction', () => { - afterEach(() => { - cleanup() - window.history.replaceState({}, '', '/') - }) - - describe('function components', () => { - test('should be reactive with withRouter(view(Comp))', () => { - const counter = store({ num: 0 }) - const MyComp = withRouter(view(() =>
{counter.num}
)) - - const { container } = render( - - - - ) - expect(container).toHaveTextContent('0') - act(() => { - counter.num++ - }) - expect(container).toHaveTextContent('1') - }) - - test('should properly route with withRouter(view(Comp))', () => { - const MyComp = withRouter( - view(() => ( -
- To Settings -

Settings

} /> -
- )) - ) - - const { container, getByText } = render( - - - - ) - - expect(container.querySelector('p')).toBe(null) - fireEvent.click(getByText('To Settings')) - expect(container.querySelector('p')).toHaveTextContent('Settings') - }) - }) - - describe('class components', () => { - test('should be reactive with withRouter(view(Comp))', () => { - const counter = store({ num: 0 }) - const MyComp = withRouter( - view( - class MyComp extends Component { - render () { - return
{counter.num}
- } - } - ) - ) - - const { container } = render( - - - - ) - expect(container).toHaveTextContent('0') - act(() => { - counter.num++ - }) - expect(container).toHaveTextContent('1') - }) - - test('should properly route with withRouter(view(Comp))', () => { - const MyComp = withRouter( - view( - class MyComp extends Component { - render () { - return ( -
- To Settings -

Settings

} /> -
- ) - } - } - ) - ) - - const { container, getByText } = render( - - - - ) - - expect(container.querySelector('p')).toBe(null) - fireEvent.click(getByText('To Settings')) - expect(container.querySelector('p')).toHaveTextContent('Settings') - }) - }) -}) diff --git a/__tests__/router.test.jsx b/__tests__/router.test.jsx new file mode 100644 index 0000000..d00edf5 --- /dev/null +++ b/__tests__/router.test.jsx @@ -0,0 +1,121 @@ +import React, { Component } from 'react'; +import { + render, + cleanup, + fireEvent, + act, +} from '@testing-library/react/pure'; +// eslint-disable-next-line import/no-unresolved +import { view, store } from 'react-easy-state'; +import { + BrowserRouter as Router, + Route, + Link, + withRouter, +} from 'react-router-dom'; + +describe('withRouter interaction', () => { + afterEach(() => { + cleanup(); + window.history.replaceState({}, '', '/'); + }); + + describe('function components', () => { + test('should be reactive with withRouter(view(Comp))', () => { + const counter = store({ num: 0 }); + const MyComp = withRouter(view(() =>
{counter.num}
)); + + const { container } = render( + + + , + ); + expect(container).toHaveTextContent('0'); + act(() => { + counter.num += 1; + }); + expect(container).toHaveTextContent('1'); + }); + + test('should properly route with withRouter(view(Comp))', () => { + const MyComp = withRouter( + view(() => ( +
+ To Settings +

Settings

} /> +
+ )), + ); + + const { container, getByText } = render( + + + , + ); + + expect(container.querySelector('p')).toBe(null); + fireEvent.click(getByText('To Settings')); + expect(container.querySelector('p')).toHaveTextContent( + 'Settings', + ); + }); + }); + + describe('class components', () => { + test('should be reactive with withRouter(view(Comp))', () => { + const counter = store({ num: 0 }); + const MyComp = withRouter( + view( + class MyComp extends Component { + render() { + return
{counter.num}
; + } + }, + ), + ); + + const { container } = render( + + + , + ); + expect(container).toHaveTextContent('0'); + act(() => { + counter.num += 1; + }); + expect(container).toHaveTextContent('1'); + }); + + test('should properly route with withRouter(view(Comp))', () => { + const MyComp = withRouter( + view( + class MyComp extends Component { + render() { + return ( +
+ To Settings +

Settings

} + /> +
+ ); + } + }, + ), + ); + + const { container, getByText } = render( + + + , + ); + + expect(container.querySelector('p')).toBe(null); + fireEvent.click(getByText('To Settings')); + expect(container.querySelector('p')).toHaveTextContent( + 'Settings', + ); + }); + }); +}); diff --git a/__tests__/staticProps.test.js b/__tests__/staticProps.test.js index 3eb35b6..ddd9151 100644 --- a/__tests__/staticProps.test.js +++ b/__tests__/staticProps.test.js @@ -1,59 +1,63 @@ -import { Component } from 'react' -import { view } from 'react-easy-state' +/* eslint-disable react/forbid-foreign-prop-types */ +/* eslint-disable no-multi-assign */ +import { Component } from 'react'; +// eslint-disable-next-line import/no-unresolved +import { view } from 'react-easy-state'; describe('static props', () => { test('view() should proxy static properties from wrapped components', () => { class Comp extends Component {} - function FuncComp () {} - - Comp.displayName = FuncComp.displayName = 'Name' - Comp.contextTypes = FuncComp.contextTypes = {} - Comp.propTypes = FuncComp.propTypes = {} - Comp.defaultProps = FuncComp.defaultProps = {} - Comp.customProp = FuncComp.customProp = {} - - const ViewComp = view(Comp) - const ViewFuncComp = view(FuncComp) - - expect(ViewComp.displayName).toBe(Comp.displayName) - expect(ViewComp.contextTypes).toBe(Comp.contextTypes) - expect(ViewComp.propTypes).toBe(Comp.propTypes) - expect(ViewComp.defaultProps).toBe(Comp.defaultProps) - expect(ViewComp.customProp).toBe(Comp.customProp) - - expect(ViewFuncComp.displayName).toBe(FuncComp.displayName) - expect(ViewFuncComp.contextTypes).toBe(FuncComp.contextTypes) - expect(ViewFuncComp.propTypes).toBe(FuncComp.propTypes) - expect(ViewFuncComp.defaultProps).toBe(FuncComp.defaultProps) - expect(ViewFuncComp.customProp).toBe(FuncComp.customProp) - }) + function FuncComp() {} + + Comp.displayName = FuncComp.displayName = 'Name'; + Comp.contextTypes = FuncComp.contextTypes = {}; + Comp.propTypes = FuncComp.propTypes = {}; + Comp.defaultProps = FuncComp.defaultProps = {}; + Comp.customProp = FuncComp.customProp = {}; + + const ViewComp = view(Comp); + const ViewFuncComp = view(FuncComp); + + expect(ViewComp.displayName).toBe(Comp.displayName); + expect(ViewComp.contextTypes).toBe(Comp.contextTypes); + expect(ViewComp.propTypes).toBe(Comp.propTypes); + expect(ViewComp.defaultProps).toBe(Comp.defaultProps); + expect(ViewComp.customProp).toBe(Comp.customProp); + + expect(ViewFuncComp.displayName).toBe(FuncComp.displayName); + expect(ViewFuncComp.contextTypes).toBe(FuncComp.contextTypes); + expect(ViewFuncComp.propTypes).toBe(FuncComp.propTypes); + expect(ViewFuncComp.defaultProps).toBe(FuncComp.defaultProps); + expect(ViewFuncComp.customProp).toBe(FuncComp.customProp); + }); test('view() should proxy static methods', () => { class Comp extends Component { - static getDerivedStateFromError () {} - static customMethod () {} + static getDerivedStateFromError() {} + + static customMethod() {} } - const ViewComp = view(Comp) + const ViewComp = view(Comp); expect(ViewComp.getDerivedStateFromError).toBe( - Comp.getDerivedStateFromError - ) - expect(ViewComp.customMethod).toBe(Comp.customMethod) - }) + Comp.getDerivedStateFromError, + ); + expect(ViewComp.customMethod).toBe(Comp.customMethod); + }); test('view() should proxy static getters', () => { class Comp extends Component { - static get defaultProp () { - return { key: 'value' } + static get defaultProp() { + return { key: 'value' }; } - static get customProp () { - return { key: 'hello' } + static get customProp() { + return { key: 'hello' }; } } - const ViewComp = view(Comp) - expect(ViewComp.defaultProps).toEqual(Comp.defaultProps) - expect(ViewComp.customProp).toEqual(Comp.customProp) - }) -}) + const ViewComp = view(Comp); + expect(ViewComp.defaultProps).toEqual(Comp.defaultProps); + expect(ViewComp.customProp).toEqual(Comp.customProp); + }); +}); diff --git a/__tests__/styled.test.js b/__tests__/styled.test.jsx similarity index 57% rename from __tests__/styled.test.js rename to __tests__/styled.test.jsx index 4c3e218..5503cd0 100644 --- a/__tests__/styled.test.js +++ b/__tests__/styled.test.jsx @@ -1,91 +1,94 @@ -import React, { Component } from 'react' -import { render, cleanup, act } from '@testing-library/react/pure' -import { view, store } from 'react-easy-state' -import { withTheme, ThemeProvider } from 'styled-components' +import React, { Component } from 'react'; +import { render, cleanup, act } from '@testing-library/react/pure'; +// eslint-disable-next-line import/no-unresolved +import { view, store } from 'react-easy-state'; +import { withTheme, ThemeProvider } from 'styled-components'; describe('withRouter interaction', () => { - const theme = { color: 'red' } + const theme = { color: 'red' }; const Theme = ({ children }) => ( {children} - ) + ); afterEach(() => { - cleanup() - }) + cleanup(); + }); describe('function components', () => { test('should be reactive with withTheme(view(Comp))', () => { - const counter = store({ num: 0 }) - const MyComp = withTheme(view(() =>
{counter.num}
)) + const counter = store({ num: 0 }); + const MyComp = withTheme(view(() =>
{counter.num}
)); const { container } = render( - - ) - expect(container).toHaveTextContent('0') + , + ); + expect(container).toHaveTextContent('0'); act(() => { - counter.num++ - }) - expect(container).toHaveTextContent('1') - }) + counter.num += 1; + }); + expect(container).toHaveTextContent('1'); + }); test('should properly inject theme with withTheme(view(Comp))', () => { const MyComp = withTheme( - view(({ theme }) =>

Hello

) - ) + view(({ theme: hocTheme }) => ( +

Hello

+ )), + ); const { container } = render( - - ) - expect(container.querySelector('p')).toHaveStyle('color: red;') - }) - }) + , + ); + expect(container.querySelector('p')).toHaveStyle('color: red;'); + }); + }); describe('class components', () => { test('should be reactive with withTheme(view(Comp))', () => { - const counter = store({ num: 0 }) + const counter = store({ num: 0 }); const MyComp = withTheme( view( class MyComp extends Component { - render () { - return
{counter.num}
+ render() { + return
{counter.num}
; } - } - ) - ) + }, + ), + ); const { container } = render( - - ) - expect(container).toHaveTextContent('0') + , + ); + expect(container).toHaveTextContent('0'); act(() => { - counter.num++ - }) - expect(container).toHaveTextContent('1') - }) + counter.num += 1; + }); + expect(container).toHaveTextContent('1'); + }); test('should properly route with withRouter(view(Comp))', () => { const MyComp = withTheme( view( class MyComp extends Component { - render () { - return

Hello

+ render() { + return

Hello

; } - } - ) - ) + }, + ), + ); const { container } = render( - - ) - expect(container.querySelector('p')).toHaveStyle('color: red;') - }) - }) -}) + , + ); + expect(container.querySelector('p')).toHaveStyle('color: red;'); + }); + }); +}); diff --git a/jest.native.json b/jest.native.json index 01961dc..e4870dd 100644 --- a/jest.native.json +++ b/jest.native.json @@ -1,6 +1,6 @@ { "preset": "react-native", - "testRegex": "\\.test\\.native\\.js$", + "testRegex": "\\.test\\.native\\.jsx?$", "transform": { "^.+\\.(js|jsx)$": "react-native/jest/preprocessor.js" }, diff --git a/jest.web.json b/jest.web.json index 136b086..1e361f7 100644 --- a/jest.web.json +++ b/jest.web.json @@ -1,7 +1,7 @@ { "setupFilesAfterEnv": ["./scripts/testSetup.js"], "testURL": "http://react-easy-state.com", - "testRegex": "\\.test\\.js$", + "testRegex": "\\.test\\.jsx?$", "collectCoverage": true, "coverageReporters": ["lcovonly", "text"], "collectCoverageFrom": [ diff --git a/package-lock.json b/package-lock.json index c488eea..2f16e36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15370,15 +15370,6 @@ "yallist": "^2.1.2" } }, - "magic-string": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", - "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -17864,18 +17855,6 @@ "object-assign": "^4.1.1", "prop-types": "^15.6.2", "scheduler": "^0.18.0" - }, - "dependencies": { - "scheduler": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", - "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", - "dev": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - } } }, "react-is": { @@ -18682,308 +18661,6 @@ } } }, - "rollup-plugin-replace": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-2.2.0.tgz", - "integrity": "sha512-/5bxtUPkDHyBJAKketb4NfaeZjL5yLZdeUihSfbF2PQMz+rSTEb8ARKoOl3UBT4m7/X+QOXJo3sLTcq+yMMYTA==", - "dev": true, - "requires": { - "magic-string": "^0.25.2", - "rollup-pluginutils": "^2.6.0" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "estree-walker": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.0.tgz", - "integrity": "sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw==", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "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" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "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" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "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" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "rollup-pluginutils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.7.1.tgz", - "integrity": "sha512-3nRf3buQGR9qz/IsSzhZAJyoK663kzseps8itkYHr+Z7ESuaffEPfgRinxbCRA0pf0gzLqkNKkSb8aNVTq75NA==", - "dev": true, - "requires": { - "estree-walker": "^0.6.0", - "micromatch": "^3.1.10" - } - } - } - }, "rollup-pluginutils": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", @@ -19373,6 +19050,16 @@ "xmlchars": "^2.1.1" } }, + "scheduler": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", @@ -19772,12 +19459,6 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, - "sourcemap-codec": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", - "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==", - "dev": true - }, "spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", diff --git a/package.json b/package.json index a8fe7c2..5526710 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "test-builds": "node ./scripts/testBuilds.js", "lint": "eslint --max-warnings 0 --ext js,jsx src scripts", "lint-fix": "eslint --max-warnings 0 --ext js,jsx src scripts --fix", + "lint-tests": "eslint --max-warnings 0 --ext js,jsx __tests__ __mocks__", + "lint-tests-fix": "eslint --max-warnings 0 --ext js,jsx __tests__ __mocks__ --fix", "install-examples": "node ./scripts/installExamples.js", "build-examples": "node ./scripts/buildExamples.js", "link-examples": "node ./scripts/linkExamples.js", @@ -97,7 +99,6 @@ "rollup-plugin-auto-external": "^2.0.0", "rollup-plugin-babel": "^4.3.3", "rollup-plugin-node-resolve": "^5.2.0", - "rollup-plugin-replace": "^2.2.0", "sinon": "^8.1.1", "styled-components": "^5.0.1" }, @@ -106,7 +107,7 @@ }, "husky": { "hooks": { - "pre-commit": "npm run lint", + "pre-commit": "npm run lint && npm run lint-tests", "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }, diff --git a/rollup.config.js b/rollup.config.js index 681efc7..c4969f8 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,110 +1,69 @@ -import path from 'path' -import replacePlugin from 'rollup-plugin-replace' -import resolvePlugin from 'rollup-plugin-node-resolve' -import babelPlugin from 'rollup-plugin-babel' -import externalsPlugin from 'rollup-plugin-auto-external' +import path from 'path'; +import resolvePlugin from 'rollup-plugin-node-resolve'; +import babelPlugin from 'rollup-plugin-babel'; +import externalsPlugin from 'rollup-plugin-auto-external'; export default [ - { - input: path.resolve('src/platforms/dom.js'), - external: ['react-dom'], - output: [ - { - format: 'es', - dir: 'dist', - entryFileNames: 'react-platform.js' - }, - { - format: 'cjs', - dir: 'dist', - entryFileNames: 'react-platform.cjs.js' - } - ] - }, - { - input: path.resolve('src/platforms/native.js'), - external: ['react-native'], - output: [ - { - format: 'es', - dir: 'dist', - entryFileNames: 'react-platform.native.js' - }, - { - format: 'cjs', - dir: 'dist', - entryFileNames: 'react-platform.cjs.native.js' - } - ] - }, { input: path.resolve('src/index.js'), - external: ['./react-platform'], plugins: [ - replacePlugin({ 'react-platform': './react-platform' }), resolvePlugin(), babelPlugin({ exclude: 'node_modules/**' }), - externalsPlugin({ dependencies: true, peerDependecies: true }) + externalsPlugin({ dependencies: true, peerDependecies: true }), ], output: { format: 'es', dir: 'dist', entryFileNames: 'es.es6.js', - sourcemap: true - } + sourcemap: true, + }, }, { input: path.resolve('src/index.js'), - external: ['./react-platform'], plugins: [ - replacePlugin({ 'react-platform': './react-platform' }), resolvePlugin(), babelPlugin({ exclude: 'node_modules/**', - presets: ['@babel/preset-env'] + presets: ['@babel/preset-env'], }), - externalsPlugin({ dependencies: true, peerDependecies: true }) + externalsPlugin({ dependencies: true, peerDependecies: true }), ], output: { format: 'es', dir: 'dist', entryFileNames: 'es.es5.js', - sourcemap: true - } + sourcemap: true, + }, }, { input: path.resolve('src/index.js'), - external: ['./react-platform.cjs'], plugins: [ - replacePlugin({ 'react-platform': './react-platform.cjs' }), resolvePlugin(), babelPlugin({ exclude: 'node_modules/**' }), - externalsPlugin({ dependencies: true, peerDependecies: true }) + externalsPlugin({ dependencies: true, peerDependecies: true }), ], output: { format: 'cjs', dir: 'dist', entryFileNames: 'cjs.es6.js', - sourcemap: true - } + sourcemap: true, + }, }, { input: path.resolve('src/index.js'), - external: ['./react-platform.cjs'], plugins: [ - replacePlugin({ 'react-platform': './react-platform.cjs' }), resolvePlugin(), babelPlugin({ exclude: 'node_modules/**', - presets: ['@babel/preset-env'] + presets: ['@babel/preset-env'], }), - externalsPlugin({ dependencies: true, peerDependecies: true }) + externalsPlugin({ dependencies: true, peerDependecies: true }), ], output: { format: 'cjs', dir: 'dist', entryFileNames: 'cjs.es5.js', - sourcemap: true - } - } -] + sourcemap: true, + }, + }, +]; diff --git a/scripts/testBuilds.js b/scripts/testBuilds.js index 54d6cb1..74c3bec 100644 --- a/scripts/testBuilds.js +++ b/scripts/testBuilds.js @@ -6,11 +6,7 @@ const { exec } = require('child_process'); const distPath = path.resolve('dist'); const files = fs .readdirSync(distPath) - .filter( - dist => - dist.indexOf('map') === -1 && - dist.indexOf('react-platform') === -1, - ); + .filter(dist => dist.indexOf('map') === -1); function execPromise(cmd) { return new Promise(resolve => exec(cmd, resolve)); diff --git a/src/platforms/dom.js b/src/platforms/dom.js deleted file mode 100644 index 2a69d4b..0000000 --- a/src/platforms/dom.js +++ /dev/null @@ -1 +0,0 @@ -export * from 'react-dom'; diff --git a/src/platforms/native.js b/src/platforms/native.js deleted file mode 100644 index f58ecd3..0000000 --- a/src/platforms/native.js +++ /dev/null @@ -1 +0,0 @@ -export * from 'react-native';