covering all javascript

This commit is contained in:
Benjamin Boudreau 2018-06-03 14:28:12 -04:00
parent afc2709742
commit e5c61ce998
10 changed files with 335 additions and 1966 deletions

View File

@ -2,3 +2,9 @@ import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16'; import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() }); Enzyme.configure({ adapter: new Adapter() });
Object.defineProperty(document, 'readyState', {
val: 'complete',
get() { return this.val; },
set(s) { this.val = s; },
});

1961
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,8 @@
"scripts": { "scripts": {
"build": "webpack", "build": "webpack",
"start": "webpack-serve --config ./webpack.config.js --mode development --open", "start": "webpack-serve --config ./webpack.config.js --mode development --open",
"test": "jest" "test": "jest",
"test-update-snapshots": "jest --updateSnapshot"
}, },
"babel": { "babel": {
"presets": [ "presets": [
@ -17,6 +18,13 @@
"jest": { "jest": {
"coverageDirectory": "./coverage/", "coverageDirectory": "./coverage/",
"collectCoverage": true, "collectCoverage": true,
"collectCoverageFrom": [
"<rootDir>/src/**/*.js?(x)"
],
"coveragePathIgnorePatterns": [
"/node_modules/",
"/test-helpers/"
],
"transform": { "transform": {
"^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest", "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.(css|scss)$": "<rootDir>/jest/cssTransform.js", "^.+\\.(css|scss)$": "<rootDir>/jest/cssTransform.js",
@ -24,10 +32,16 @@
}, },
"setupFiles": [ "setupFiles": [
"<rootDir>/jest/setup.js" "<rootDir>/jest/setup.js"
],
"snapshotSerializers": [
"enzyme-to-json/serializer"
] ]
}, },
"serve": { "serve": {
"content": ["./dist", "./public"] "content": [
"./dist",
"./public"
]
}, },
"author": "seriousben https://github.com/seriousben", "author": "seriousben https://github.com/seriousben",
"license": "MIT", "license": "MIT",
@ -41,6 +55,7 @@
"css-loader": "^0.28.11", "css-loader": "^0.28.11",
"enzyme": "^3.3.0", "enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1", "enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "^3.3.4",
"eslint": "^4.19.1", "eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0", "eslint-config-airbnb": "^16.1.0",
"eslint-import-resolver-webpack": "^0.10.0", "eslint-import-resolver-webpack": "^0.10.0",

View File

@ -6,7 +6,7 @@
<body> <body>
<script src="/embeddable-widget.js"></script> <script src="/embeddable-widget.js"></script>
<script> <script>
EmbeddableWidget.render(); EmbeddableWidget.mount();
</script> </script>
</body> </body>
</html> </html>

View File

@ -1 +0,0 @@
58305

View File

@ -0,0 +1,175 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Widget /> open/close 1`] = `
<Widget>
<div
className="docked-widget"
>
<Transition
appear={false}
enter={true}
exit={true}
in={false}
mountOnEnter={false}
onEnter={[Function]}
onEntered={[Function]}
onEntering={[Function]}
onExit={[Function]}
onExited={[Function]}
onExiting={[Function]}
timeout={250}
unmountOnExit={false}
>
<div
className="widget widget-exited"
>
<div
className="widget-header"
>
<div
className="widget-header-title"
>
Header
</div>
<a
className="widget-header-icon"
onClick={[Function]}
>
X
</a>
</div>
<div
className="widget-body"
>
Body
</div>
<div
className="widget-footer"
>
Footer
</div>
</div>
</Transition>
<a
className="dock"
onClick={[Function]}
>
^ OPEN ^
</a>
</div>
</Widget>
`;
exports[`<Widget /> open/close 2`] = `
<Widget>
<div
className="docked-widget"
>
<Transition
appear={false}
enter={true}
exit={true}
in={true}
mountOnEnter={false}
onEnter={[Function]}
onEntered={[Function]}
onEntering={[Function]}
onExit={[Function]}
onExited={[Function]}
onExiting={[Function]}
timeout={250}
unmountOnExit={false}
>
<div
className="widget widget-entering"
>
<div
className="widget-header"
>
<div
className="widget-header-title"
>
Header
</div>
<a
className="widget-header-icon"
onClick={[Function]}
>
X
</a>
</div>
<div
className="widget-body"
>
Body
</div>
<div
className="widget-footer"
>
Footer
</div>
</div>
</Transition>
</div>
</Widget>
`;
exports[`<Widget /> open/close 3`] = `
<Widget>
<div
className="docked-widget"
>
<Transition
appear={false}
enter={true}
exit={true}
in={false}
mountOnEnter={false}
onEnter={[Function]}
onEntered={[Function]}
onEntering={[Function]}
onExit={[Function]}
onExited={[Function]}
onExiting={[Function]}
timeout={250}
unmountOnExit={false}
>
<div
className="widget widget-exited"
>
<div
className="widget-header"
>
<div
className="widget-header-title"
>
Header
</div>
<a
className="widget-header-icon"
onClick={[Function]}
>
X
</a>
</div>
<div
className="widget-body"
>
Body
</div>
<div
className="widget-footer"
>
Footer
</div>
</div>
</Transition>
<a
className="dock"
onClick={[Function]}
>
^ OPEN ^
</a>
</div>
</Widget>
`;

View File

@ -3,16 +3,22 @@ import ReactDOM from 'react-dom';
import Widget from './widget'; import Widget from './widget';
export default class EmbeddableWidget { export default class EmbeddableWidget {
static render() { static el;
static mount() {
const component = <Widget />; const component = <Widget />;
function doRender() { function doRender() {
const containerEl = document.createElement('div'); if (EmbeddableWidget.el) {
document.body.appendChild(containerEl); throw new Error('EmbeddableWidget is already mounted, unmount first');
}
const el = document.createElement('div');
document.body.appendChild(el);
ReactDOM.render( ReactDOM.render(
component, component,
containerEl, el,
); );
EmbeddableWidget.el = el;
} }
if (document.readyState === 'complete') { if (document.readyState === 'complete') {
doRender(); doRender();
@ -22,4 +28,13 @@ export default class EmbeddableWidget {
}); });
} }
} }
static unmount() {
if (!EmbeddableWidget.el) {
throw new Error('EmbeddableWidget is not mounted, mount first');
}
ReactDOM.unmountComponentAtNode(EmbeddableWidget.el);
EmbeddableWidget.el.parentNode.removeChild(EmbeddableWidget.el);
EmbeddableWidget.el = null;
}
} }

44
src/index.test.js Normal file
View File

@ -0,0 +1,44 @@
import EmbeddableWidget from './index';
import { waitForSelection } from './test-helpers';
describe('EmbeddableWidget', () => {
afterEach(() => {
document.readyState = 'complete';
if (EmbeddableWidget.el) {
EmbeddableWidget.unmount();
}
});
test('#mount document becomes ready', async () => {
document.readyState = 'loading';
EmbeddableWidget.mount();
window.dispatchEvent(new Event('load', {}));
await waitForSelection(document, 'div');
});
test('#mount document complete', async () => {
EmbeddableWidget.mount();
await waitForSelection(document, 'div');
});
test('#mount twice', async () => {
EmbeddableWidget.mount();
expect(() => EmbeddableWidget.mount()).toThrow('already mounted');
});
test('#unmount', async () => {
const el = document.createElement('div');
document.body.appendChild(el);
expect(document.querySelectorAll('div')).toHaveLength(1);
EmbeddableWidget.el = el;
EmbeddableWidget.unmount();
expect(document.querySelectorAll('div')).toHaveLength(0);
});
test('#unmount without mounting', async () => {
expect(() => EmbeddableWidget.unmount()).toThrow('not mounted');
});
});

30
src/test-helpers/index.js Normal file
View File

@ -0,0 +1,30 @@
function checkFunc(dom, selector) {
if (typeof dom.update === 'function') {
const el = dom.update().find(selector);
if (el.exists()) {
return el;
}
return null;
}
const els = dom.querySelectorAll(selector);
if (els.length !== 0) {
return els;
}
return null;
}
export async function waitForSelection(dom, selector) {
let numSleep = 0;
for (;;) {
const el = checkFunc(dom, selector);
if (el) {
return el;
}
if (numSleep > 2) {
throw new Error(`could not find ${selector}`);
}
await new Promise(resolve => setTimeout(resolve, 250));
numSleep += 1;
}
}

View File

@ -1,9 +1,37 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { mount } from 'enzyme';
import Widget from './widget'; import Widget from './widget';
import { waitForSelection } from './test-helpers';
describe('<Widget />', () => {
test('open/close', async () => {
const widgetDom = mount(<Widget />);
expect(widgetDom).toMatchSnapshot();
{
const dockAnchorEl = widgetDom.find('a.dock');
expect(dockAnchorEl).toHaveLength(1);
// open widget
dockAnchorEl.simulate('click');
}
expect(widgetDom).toMatchSnapshot();
// dock does not exist anymore
expect(widgetDom.find('a.dock')).toHaveLength(0);
const closeAnchorEl = await waitForSelection(widgetDom, 'a.widget-header-icon');
expect(closeAnchorEl).toHaveLength(1);
// close widget
closeAnchorEl.simulate('click');
{
const dockAnchorEl = await waitForSelection(widgetDom, 'a.dock');
expect(dockAnchorEl).toHaveLength(1);
}
expect(widgetDom).toMatchSnapshot();
});
test('simple test', () => {
const widgetDom = shallow(<Widget />);
console.log(widgetDom.html());
expect(widgetDom.find('.docked-widget').exists()).toBe(true);
}); });