Skip to content

Commit

Permalink
add browser test suite (zenoamaro#581)
Browse files Browse the repository at this point in the history
* add in-browser smoke tests with cypress

* add cypress intellisense

* add prettier config matching current style

* update cypress

* add more e2e tests

- instead of using demo page, use a test harness that allows more inspection of the editor instance
- run the old test suite inside the browser (!! it works, just requires one test to be rewritten to fully catch errors instead of hiding them)
  • Loading branch information
alexkrolick authored Apr 10, 2020
1 parent 47f8a4e commit 024827c
Show file tree
Hide file tree
Showing 10 changed files with 932 additions and 99 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@
# Packaging artifacts
/react-quill-*.tgz
/package

# Test artifacts
.cypress/screenshots
.cypress/videos
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "es5"
}
10 changes: 10 additions & 0 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"integrationFolder": "test",
"fileServerFolder": "demo",
"testFiles": "*.spec.js",
"videosFolder": ".cypress/videos",
"screenshotsFolder": ".cypress/screenshots",
"fixturesFolder": false,
"pluginsFile": false,
"supportFile": false
}
7 changes: 7 additions & 0 deletions demo/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="quill.snow.css" />
</head>
<body>
<div id="app"></div>
</body>
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
"build:dist": "webpack",
"build:css": "cpx 'node_modules/quill/dist/quill.*.css' dist",
"watch": "tsc --watch",
"test": "npm run build && npm run test:unit && npm run test:coverage",
"pretest": "npm run build",
"test": "npm run test:unit && npm run test:coverage && npm run test:browser",
"test:unit": "mocha --recursive --require=./test/setup.js -R spec test/index",
"test:coverage": "mocha --recursive --require=./test/setup.js -R mocha-text-cov test/index",
"test:browser": "cypress run",
"test:browser:interactive": "cypress open",
"demo": "superstatic demo",
"clean": "rimraf lib dist",
"prepublishOnly": "npm run build"
Expand Down Expand Up @@ -49,17 +52,18 @@
"react-dom": "^16.0.0"
},
"devDependencies": {
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.4",
"@types/lodash": "^4.14.146",
"@types/chai": "^4.2.11",
"@types/lodash": "^4.14.146",
"@types/mocha": "^7.0.2",
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.4",
"@types/sinon": "^7.5.2",
"blanket": "^1.2.3",
"chai": "^4.2.0",
"chai-enzyme": "^1.0.0-beta.1",
"cheerio": "^1.0.0-rc.3",
"cpx": "^1.5.0",
"cypress": "^4.3.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"jsdom": "^11.0.0",
Expand Down
93 changes: 54 additions & 39 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@

const React = require('react');
const sinon = require('sinon');
const {expect} = require('chai');
const ReactQuill = require('../lib/index');
const {Quill} = require('../lib/index');
const { Quill } = require('../lib/index');

const {
mountReactQuill,
Expand All @@ -20,15 +19,16 @@ const {
withMockedConsole,
} = require('./utils');

console.log('\n\
console.log(
'\n\
Note that some functionality cannot be tested outside of a full browser environment.\n\n\
To manually test the component:\n\
1) Run "npm install" \& "npm run build"\n\
1) Run "npm install" & "npm run build"\n\
2) Open "demo/index.html" in a web browser.\
');
'
);

describe('<ReactQuill />', function() {

it('calls componentDidMount', () => {
sinon.spy(ReactQuill.prototype, 'componentDidMount');
const wrapper = mountReactQuill();
Expand All @@ -37,18 +37,18 @@ describe('<ReactQuill />', function() {
});

it('allows props to be set', () => {
const props = {readOnly: true}
const props = { readOnly: true };
const wrapper = mountReactQuill(props);
expect(wrapper.props().readOnly).to.equal(true);
wrapper.setProps({readOnly: false});
wrapper.setProps({ readOnly: false });
expect(wrapper.props().readOnly).to.equal(false);
});

it('attaches a Quill instance to the component', () => {
const wrapper = mountReactQuill();
const quill = getQuillInstance(wrapper);
expect(quill instanceof Quill).to.equal(true);
})
});

it('passes options to Quill from props', () => {
const enabledFormats = ['underline', 'bold', 'italic'];
Expand All @@ -66,85 +66,101 @@ describe('<ReactQuill />', function() {
expect(quill.options.readOnly).to.equal(props.readOnly);
expect(quill.options.modules).to.include.keys(Object.keys(props.modules));
expect(quill.options.formats).to.include.members(props.formats);
})
});

it('allows using HTML strings as value', () => {
const html = '<p>Hello, world!</p>';
const wrapper = mountReactQuill({value: html});
const wrapper = mountReactQuill({ value: html });
const quill = getQuillInstance(wrapper);
expect(getQuillContentsAsHTML(wrapper)).to.equal(html);
});

it('allows using HTML strings as defaultValue', () => {
const html = '<p>Hello, world!</p>';
const wrapper = mountReactQuill({defaultValue: html});
const wrapper = mountReactQuill({ defaultValue: html });
const quill = getQuillInstance(wrapper);
expect(getQuillContentsAsHTML(wrapper)).to.equal(html);
});

it('allows using Deltas as value', () => {
const html = '<p>Hello, world!</p>';
const delta = {ops: [{insert: 'Hello, world!'}]};
const wrapper = mountReactQuill({value: html});
const delta = { ops: [{ insert: 'Hello, world!' }] };
const wrapper = mountReactQuill({ value: html });
const quill = getQuillInstance(wrapper);
expect(getQuillContentsAsHTML(wrapper)).to.equal(html);
});

it('prevents using Delta changesets from events as value', (done) => {
it('prevents using Delta changesets from events as value', done => {
const value = '<p>Hello, world!</p>';
const changedValue = '<p>Adieu, world!</p>';
let wrapper;
const onChange = (_, delta) => {
withMockedConsole(() => {
expect(() => wrapper.setProps({value: delta})).to.throw();
done();
});
wrapper.setProps({ value: delta });
};
const wrapper = mountReactQuill({value, onChange});
wrapper = mountReactQuill({ value, onChange });

const expectedErr = /You are passing the `delta` object from the `onChange` event back/;
// this test knows a lot about the implementation,
// but we need to wrap the right function with a catch
// in order to prevent errors from it from propagating
const origValidateProps = wrapper.instance().validateProps;
let calledDone = false; // might get called more than once
wrapper.instance().validateProps = function(props) {
try {
origValidateProps.call(wrapper.instance(), props);
} catch (err) {
if (expectedErr.test(err) && calledDone === false) {
done();
calledDone = true;
}
}
}.bind(wrapper.instance());

setQuillContentsFromHTML(wrapper, changedValue);
});

it('allows using Deltas as defaultValue', () => {
const html = '<p>Hello, world!</p>';
const delta = {ops: [{insert: 'Hello, world!'}]};
const wrapper = mountReactQuill({defaultValue: html});
const delta = { ops: [{ insert: 'Hello, world!' }] };
const wrapper = mountReactQuill({ defaultValue: html });
const quill = getQuillInstance(wrapper);
expect(getQuillContentsAsHTML(wrapper)).to.equal(html);
});

it('calls onChange with the new value when Quill calls pasteHTML', () => {
const onChangeSpy = sinon.spy();
const inHtml = '<p>Hello, world!</p>';
const onChange = (value) => {
const onChange = value => {
expect(inHtml).to.equal(value);
onChangeSpy();
};
const wrapper = mountReactQuill({onChange});
const wrapper = mountReactQuill({ onChange });
setQuillContentsFromHTML(wrapper, inHtml);
expect(getQuillContentsAsHTML(wrapper)).to.equal(inHtml)
expect(getQuillContentsAsHTML(wrapper)).to.equal(inHtml);
expect(onChangeSpy).to.have.property('callCount', 1);
})
});

it('calls onChange with the new value when Quill calls insertText', () => {
const onChangeSpy = sinon.spy();
const inHtml = '<p><strong>Hello, World!</strong></p>';
const onChange = (value) => {
const onChange = value => {
expect(inHtml).to.equal(value);
onChangeSpy();
};
const wrapper = mountReactQuill({onChange});
const wrapper = mountReactQuill({ onChange });
const quill = getQuillInstance(wrapper);
quill.insertText(0, 'Hello, World!', 'bold', true);
expect(getQuillContentsAsHTML(wrapper)).to.equal(inHtml);
expect(onChangeSpy).to.have.property('callCount', 1);
})
});

it('shows defaultValue if value prop is undefined', () => {
const defaultValue = '<p>Hello, world!</p>';
const wrapper = mountReactQuill({defaultValue});
const wrapper = mountReactQuill({ defaultValue });
const quill = getQuillInstance(wrapper);
// @ts-ignore untyped instance
expect(wrapper.instance().getEditorContents()).to.equal(defaultValue);
})
});

it('shows the value prop instead of defaultValue if both are defined', () => {
const defaultValue = '<p>Hello, world!</p>';
Expand All @@ -156,15 +172,15 @@ describe('<ReactQuill />', function() {
const quill = getQuillInstance(wrapper);
// @ts-ignore untyped instance
expect(wrapper.instance().getEditorContents()).to.equal(value);
})
});

it('uses a custom editing area if provided', () => {
const div = React.createFactory('div');
const editingArea = div({id:'venus'});
const editingArea = div({ id: 'venus' });
const wrapper = mountReactQuill({}, editingArea);
const quill = getQuillInstance(wrapper);
expect(wrapper.getDOMNode().querySelector('div#venus')).not.to.be.null;
})
});

/**
* This can't be tested with the current state of JSDOM.
Expand All @@ -173,26 +189,25 @@ describe('<ReactQuill />', function() {
* https://github.com/tmpvar/jsdom/issues/317.
* Leaving this pending test as a reminder to follow up.
*/
it('focuses editor when calling focus()')
it('focuses editor when calling focus()');

/**
* A test for this may work if checking document.activeElement,
* but chances are the focus was never removed from the body
* after calling focus(). See JSDOM issue #317.
*/
it('removes focus from the editor when calling blur()')
it('removes focus from the editor when calling blur()');

/**
* In a browser, querySelector('.ql-editor').textContent = 'hi' would
* trigger a 'text-change' event, but here it doesn't. Is the polyfill
* for MutationObserver not working?
*/
it('calls onChange after the textContent of the editor changes')
it('calls onChange after the textContent of the editor changes');

/**
* This is hard to do without Selenium's 'type' function, but it is the
* ultimate test of whether everything is working or not
*/
it('calls onChange after keypresses are sent to the editor')

it('calls onChange after keypresses are sent to the editor');
});
Loading

0 comments on commit 024827c

Please sign in to comment.