diff --git a/.all-contributorsrc b/.all-contributorsrc index 2c085cca..8e42dc90 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -498,6 +498,15 @@ "code", "test" ] + }, + { + "login": "Tolsee", + "name": "Tulsi Sapkota", + "avatar_url": "https://avatars0.githubusercontent.com/u/16590492?v=4", + "profile": "https://github.com/Tolsee", + "contributions": [ + "code" + ] } ] } diff --git a/.travis.yml b/.travis.yml index c845ab33..19e2290f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ cache: notifications: email: false node_js: - - 'node' - '10' - '8' install: npm install diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4bac2f05..55ff9fed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,21 +27,6 @@ Thanks for being willing to contribute! > branch. Whenever you want to update your version of `master`, do a regular > `git pull`. -## Add yourself as a contributor - -This project follows the [all contributors][all-contributors] specification. -To add yourself to the table of contributors on the `README.md`, please use the -automated script as part of your PR: - -```console -npm run add-contributor -``` - -Follow the prompt and commit `.all-contributorsrc` and `README.md` in the PR. -If you've already added yourself to the list and are making -a new type of contribution, you can run it again and select the added -contribution type. - ## Committing and Pushing changes Please make sure to run the tests before you commit your changes. You can run diff --git a/README.md b/README.md index 27061d52..643a2103 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ [![downloads][downloads-badge]][npmtrends] [![MIT License][license-badge]][license] -[![All Contributors](https://img.shields.io/badge/all_contributors-50-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-51-orange.svg?style=flat-square)](#contributors) [![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc] @@ -138,7 +138,7 @@ Thanks goes to these people ([emoji key][emojis]): | [dadamssg
dadamssg](https://github.com/dadamssg)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=dadamssg "Code") | [Neil Kistner
Neil Kistner](https://neilkistner.com/)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=wyze "Code") | [Ben Chauvette
Ben Chauvette](http://bdchauvette.net/)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=bdchauvette "Code") | [Jeff Baumgardt
Jeff Baumgardt](https://github.com/JeffBaumgardt)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=JeffBaumgardt "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=JeffBaumgardt "Documentation") | [Matan Kushner
Matan Kushner](http://matchai.me)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=matchai "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=matchai "Documentation") [🤔](#ideas-matchai "Ideas, Planning, & Feedback") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=matchai "Tests") | [Alex Wendte
Alex Wendte](http://www.wendtedesigns.com/)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=themostcolm "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=themostcolm "Documentation") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=themostcolm "Tests") | [Tamas Fodor
Tamas Fodor](https://github.com/ruffle1986)
[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=ruffle1986 "Documentation") | | [Benjamin Eckardt
Benjamin Eckardt](https://github.com/BenjaminEckardt)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=BenjaminEckardt "Code") | [Ryan Campbell
Ryan Campbell](https://github.com/campbellr)
[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=campbellr "Documentation") | [Taylor Briggs
Taylor Briggs](https://taylor-briggs.com)
[⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=TaylorBriggs "Tests") | [John Gozde
John Gozde](https://github.com/jgoz)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=jgoz "Code") | [C. T. Lin
C. T. Lin](https://github.com/chentsulin)
[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=chentsulin "Documentation") | [Terrence Wong
Terrence Wong](http://terrencewwong.com)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=terrencewwong "Code") | [Soo Jae Hwang
Soo Jae Hwang](https://www.ossfinder.com)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=misoguy "Code") | | [Royston Shufflebotham
Royston Shufflebotham](https://github.com/RoystonS)
[🐛](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3ARoystonS "Bug reports") [💻](https://github.com/kentcdodds/dom-testing-library/commits?author=RoystonS "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=RoystonS "Documentation") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=RoystonS "Tests") | [Vadim Brodsky
Vadim Brodsky](http://www.vadimbrodsky.com)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=VadimBrodsky "Code") | [Eunjae Lee
Eunjae Lee](https://twitter.com/eunjae_lee)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=eunjae-lee "Code") | [David Peter
David Peter](http://davidpeter.me)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=sarenji "Code") | [Shy Alter
Shy Alter](https://twitter.com/@puemos)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=puemos "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=puemos "Documentation") | [Łukasz Makuch
Łukasz Makuch](https://lukaszmakuch.pl)
[📦](#platform-lukaszmakuch "Packaging/porting to new platform") | [Tyler Haas
Tyler Haas](https://github.com/tylerthehaas)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=tylerthehaas "Code") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=tylerthehaas "Tests") | -| [Vesa Laakso
Vesa Laakso](http://vesalaakso.com)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=valscion "Code") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=valscion "Tests") | +| [Vesa Laakso
Vesa Laakso](http://vesalaakso.com)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=valscion "Code") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=valscion "Tests") | [Tulsi Sapkota
Tulsi Sapkota](https://github.com/Tolsee)
[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=Tolsee "Code") | diff --git a/jest.config.js b/jest.config.js index 435c4715..758048f5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,20 +1,6 @@ const baseConfig = require('kcd-scripts/jest') module.exports = { - collectCoverageFrom: baseConfig.collectCoverageFrom, - coverageThreshold: baseConfig.coverageThreshold, - projects: [ - { - ...baseConfig, - displayName: 'jsdom', - testEnvironment: 'jest-environment-jsdom', - }, - { - ...baseConfig, - displayName: 'node', - testEnvironment: 'jest-environment-node', - }, - ], - // this is for eslint - modulePaths: baseConfig.modulePaths, + ...baseConfig, + testEnvironment: 'jest-environment-jsdom', } diff --git a/package.json b/package.json index 0436eb4c..adb6b817 100644 --- a/package.json +++ b/package.json @@ -24,32 +24,36 @@ "node": ">=8" }, "scripts": { - "add-contributor": "kcd-scripts contributors add", "build": "kcd-scripts build && kcd-scripts build --bundle --no-clean", "lint": "kcd-scripts lint", "test": "kcd-scripts test", "test:update": "npm test -- --updateSnapshot --coverage", "validate": "kcd-scripts validate", "setup": "npm install && npm run validate -s", - "precommit": "kcd-scripts precommit", "dtslint": "dtslint typings" }, + "husky": { + "hooks": { + "pre-commit": "kcd-scripts pre-commit" + } + }, "files": [ "dist", "typings" ], "dependencies": { - "@babel/runtime": "^7.1.5", + "@babel/runtime": "^7.3.4", "@sheerun/mutationobserver-shim": "^0.3.2", "pretty-format": "^24.0.0", "wait-for-expect": "^1.1.0" }, "devDependencies": { - "dtslint": "^0.3.0", - "jest-dom": "^2.1.1", + "dtslint": "^0.5.3", + "jest-dom": "^3.1.2", "jest-in-case": "^1.0.2", - "jsdom": "^13.0.0", - "kcd-scripts": "^0.46.0" + "jest-watch-select-projects": "^0.1.1", + "jsdom": "^13.2.0", + "kcd-scripts": "^1.1.0" }, "eslintConfig": { "extends": "./node_modules/kcd-scripts/eslint.js", diff --git a/src/__tests__/element-queries.js b/src/__tests__/element-queries.js index d89d9142..f6c09bb6 100644 --- a/src/__tests__/element-queries.js +++ b/src/__tests__/element-queries.js @@ -1,7 +1,6 @@ import 'jest-dom/extend-expect' import {configure} from '../config' import {render, renderIntoDocument} from './helpers/test-utils' -import document from './helpers/document' beforeEach(() => { document.defaultView.Cypress = null diff --git a/src/__tests__/events.js b/src/__tests__/events.js index a53fdf8b..198ceba0 100644 --- a/src/__tests__/events.js +++ b/src/__tests__/events.js @@ -1,6 +1,4 @@ import {fireEvent} from '..' -import document from './helpers/document' -import window from './helpers/window' const eventTypes = [ { diff --git a/src/__tests__/example.js b/src/__tests__/example.js index f6a96dd6..da0bb8ba 100644 --- a/src/__tests__/example.js +++ b/src/__tests__/example.js @@ -2,7 +2,6 @@ import {getByLabelText, getByText, getByTestId, queryByTestId, wait} from '../' // adds special assertions like toHaveTextContent import 'jest-dom/extend-expect' -import document from './helpers/document' function getExampleDOM() { // This is just a raw example of setting up some DOM diff --git a/src/__tests__/get-queries-for-element.js b/src/__tests__/get-queries-for-element.js index 44d2e43d..1a6afba8 100644 --- a/src/__tests__/get-queries-for-element.js +++ b/src/__tests__/get-queries-for-element.js @@ -1,6 +1,5 @@ import {getQueriesForElement} from '../get-queries-for-element' import {queries} from '..' -import document from './helpers/document' test('uses default queries', () => { const container = document.createElement('div') diff --git a/src/__tests__/helpers.js b/src/__tests__/helpers.js index 9e80cdb8..cbf6e628 100644 --- a/src/__tests__/helpers.js +++ b/src/__tests__/helpers.js @@ -1,17 +1,7 @@ import {getDocument, newMutationObserver} from '../helpers' -describe('getDocument', () => { - if (typeof document === 'undefined') { - test('throws an error if window does not exist', () => { - expect(() => getDocument()).toThrowError( - /Could not find default container/, - ) - }) - } else { - test('returns global document if exists', () => { - expect(getDocument()).toBe(document) - }) - } +test('returns global document if exists', () => { + expect(getDocument()).toBe(document) }) class DummyClass { diff --git a/src/__tests__/helpers/document.js b/src/__tests__/helpers/document.js deleted file mode 100644 index 1df46c07..00000000 --- a/src/__tests__/helpers/document.js +++ /dev/null @@ -1,9 +0,0 @@ -let testWindow = typeof window === 'undefined' ? undefined : window - -if (typeof window === 'undefined') { - const {JSDOM} = require('jsdom') - const dom = new JSDOM() - testWindow = dom.window -} - -module.exports = testWindow.document diff --git a/src/__tests__/helpers/test-utils.js b/src/__tests__/helpers/test-utils.js index 9a31e232..71461eba 100644 --- a/src/__tests__/helpers/test-utils.js +++ b/src/__tests__/helpers/test-utils.js @@ -1,5 +1,4 @@ import {getQueriesForElement} from '../../get-queries-for-element' -import document from './document' function render(html) { const container = document.createElement('div') diff --git a/src/__tests__/helpers/window.js b/src/__tests__/helpers/window.js deleted file mode 100644 index f9ebb36c..00000000 --- a/src/__tests__/helpers/window.js +++ /dev/null @@ -1,9 +0,0 @@ -let testWindow = typeof window === 'undefined' ? undefined : window - -if (typeof window === 'undefined') { - const {JSDOM} = require('jsdom') - const dom = new JSDOM() - testWindow = dom.window -} - -module.exports = testWindow diff --git a/src/__tests__/pretty-dom.js b/src/__tests__/pretty-dom.js index b2fe3d02..312cf78b 100644 --- a/src/__tests__/pretty-dom.js +++ b/src/__tests__/pretty-dom.js @@ -1,6 +1,5 @@ import {prettyDOM} from '../pretty-dom' import {render} from './helpers/test-utils' -import document from './helpers/document' test('it prints out the given DOM element tree', () => { const {container} = render('
Hello World!
') diff --git a/src/__tests__/wait-for-dom-change.js b/src/__tests__/wait-for-dom-change.js index 4d43d2df..0034f9bb 100644 --- a/src/__tests__/wait-for-dom-change.js +++ b/src/__tests__/wait-for-dom-change.js @@ -2,7 +2,6 @@ import {waitForDomChange} from '../' // adds special assertions like toBeTruthy import 'jest-dom/extend-expect' import {render} from './helpers/test-utils' -import document from './helpers/document' const skipSomeTime = delayMs => new Promise(resolve => setTimeout(resolve, delayMs)) @@ -16,11 +15,7 @@ test('it waits for the next DOM mutation', async () => { const successHandler = jest.fn().mockName('successHandler') const errorHandler = jest.fn().mockName('errorHandler') - if (typeof window === 'undefined') { - waitForDomChange({container: document}).then(successHandler, errorHandler) - } else { - waitForDomChange().then(successHandler, errorHandler) - } + waitForDomChange().then(successHandler, errorHandler) // Promise callbacks are always asynchronous. expect(successHandler).toHaveBeenCalledTimes(0) @@ -88,14 +83,7 @@ test('it throws if timeout is exceeded', async () => { const successHandler = jest.fn().mockName('successHandler') const errorHandler = jest.fn().mockName('errorHandler') - if (typeof window === 'undefined') { - waitForDomChange({container: document, timeout: 70}).then( - successHandler, - errorHandler, - ) - } else { - waitForDomChange({timeout: 70}).then(successHandler, errorHandler) - } + waitForDomChange({timeout: 70}).then(successHandler, errorHandler) await skipSomeTimeForMutationObserver(100) diff --git a/src/__tests__/wait-for-element-to-be-removed.fake-timers.js b/src/__tests__/wait-for-element-to-be-removed.fake-timers.js new file mode 100644 index 00000000..a1d854cb --- /dev/null +++ b/src/__tests__/wait-for-element-to-be-removed.fake-timers.js @@ -0,0 +1,40 @@ +import 'jest-dom/extend-expect' +import {waitForElementToBeRemoved} from '../' +import {render} from './helpers/test-utils' + +jest.useFakeTimers() + +test('requires a function as the first parameter', () => { + return expect( + waitForElementToBeRemoved(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"waitForElementToBeRemoved requires a function as the first parameter"`, + ) +}) + +test('requires an element to exist first', () => { + return expect( + waitForElementToBeRemoved(() => null), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The callback function which was passed did not return an element or non-empty array of elements. waitForElementToBeRemoved requires that the element(s) exist before waiting for removal."`, + ) +}) + +test('requires an unempty array of elements to exist first', () => { + return expect( + waitForElementToBeRemoved(() => []), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The callback function which was passed did not return an element or non-empty array of elements. waitForElementToBeRemoved requires that the element(s) exist before waiting for removal."`, + ) +}) + +test('times out after 4500ms by default', () => { + const {container} = render(`
`) + const promise = expect( + waitForElementToBeRemoved(() => container), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Timed out in waitForElementToBeRemoved."`, + ) + jest.advanceTimersByTime(4501) + return promise +}) diff --git a/src/__tests__/wait-for-element-to-be-removed.js b/src/__tests__/wait-for-element-to-be-removed.js new file mode 100644 index 00000000..f6425b41 --- /dev/null +++ b/src/__tests__/wait-for-element-to-be-removed.js @@ -0,0 +1,36 @@ +import 'jest-dom/extend-expect' +import {waitForElementToBeRemoved} from '../' +import {renderIntoDocument} from './helpers/test-utils' + +test('resolves on mutation only when the element is removed', async () => { + const {queryAllByTestId} = renderIntoDocument(` +
+
+ `) + const divs = queryAllByTestId('div') + // first mutation + setTimeout(() => { + divs.forEach(d => d.setAttribute('id', 'mutated')) + }) + // removal + setTimeout(() => { + divs.forEach(div => div.parentElement.removeChild(div)) + }, 100) + // the timeout is here for two reasons: + // 1. It helps test the timeout config + // 2. The element should be removed immediately + // so if it doesn't in the first 100ms then we know something's wrong + // so we'll fail early and not wait the full timeout + await waitForElementToBeRemoved(() => queryAllByTestId('div'), {timeout: 200}) +}) + +test('resolves on mutation if callback throws an error', async () => { + const {getByTestId} = renderIntoDocument(` +
+`) + const div = getByTestId('div') + setTimeout(() => { + div.parentElement.removeChild(div) + }) + await waitForElementToBeRemoved(() => getByTestId('div'), {timeout: 100}) +}) diff --git a/src/__tests__/wait-for-element.js b/src/__tests__/wait-for-element.js index aa9a8f9f..178142b8 100644 --- a/src/__tests__/wait-for-element.js +++ b/src/__tests__/wait-for-element.js @@ -2,7 +2,6 @@ import {waitForElement, wait} from '../' // adds special assertions like toBeTruthy import 'jest-dom/extend-expect' import {render} from './helpers/test-utils' -import document from './helpers/document' const skipSomeTime = delayMs => new Promise(resolve => setTimeout(resolve, delayMs)) diff --git a/src/__tests__/wait.js b/src/__tests__/wait.js index ab0290e7..7dcd56d6 100644 --- a/src/__tests__/wait.js +++ b/src/__tests__/wait.js @@ -10,14 +10,3 @@ test('it waits for the data to be loaded', async () => { await wait(() => expect(spy).toHaveBeenCalledTimes(1)) expect(spy).toHaveBeenCalledWith() }) - -test('can just be used for a next tick thing', async () => { - jest.useFakeTimers() - const spy = jest.fn() - Promise.resolve().then(spy) - expect(spy).toHaveBeenCalledTimes(0) // promises are always async - await wait() // wait for next tick - jest.advanceTimersByTime(60) - expect(spy).toHaveBeenCalledTimes(1) - jest.clearAllTimers() -}) diff --git a/src/helpers.js b/src/helpers.js index 1fefa0b8..77f68b8c 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -11,6 +11,7 @@ function newMutationObserver(onMutation) { } function getDocument() { + /* istanbul ignore if */ if (typeof window === 'undefined') { throw new Error('Could not find default container') } diff --git a/src/index.js b/src/index.js index c69df8a5..eede8d4a 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ import * as queryHelpers from './query-helpers' export * from './queries' export * from './wait' export * from './wait-for-element' +export * from './wait-for-element-to-be-removed' export * from './wait-for-dom-change' export {getDefaultNormalizer} from './matches' export * from './get-node-text' diff --git a/src/wait-for-element-to-be-removed.js b/src/wait-for-element-to-be-removed.js new file mode 100644 index 00000000..5070490d --- /dev/null +++ b/src/wait-for-element-to-be-removed.js @@ -0,0 +1,72 @@ +import {getDocument, getSetImmediate, newMutationObserver} from './helpers' + +function waitForElementToBeRemoved( + callback, + { + container = getDocument(), + timeout = 4500, + mutationObserverOptions = { + subtree: true, + childList: true, + attributes: true, + characterData: true, + }, + } = {}, +) { + return new Promise((resolve, reject) => { + if (typeof callback !== 'function') { + reject( + new Error( + 'waitForElementToBeRemoved requires a function as the first parameter', + ), + ) + } + const timer = setTimeout(onTimeout, timeout) + const observer = newMutationObserver(onMutation) + + // Check if the element is not present synchronously, + // As the name waitForElementToBeRemoved should check `present` --> `removed` + try { + const result = callback() + if (!result || (Array.isArray(result) && !result.length)) { + onDone( + new Error( + 'The callback function which was passed did not return an element or non-empty array of elements. waitForElementToBeRemoved requires that the element(s) exist before waiting for removal.', + ), + ) + } else { + // Only observe for mutations only if there is element while checking synchronously + observer.observe(container, mutationObserverOptions) + } + } catch (error) { + onDone(error) + } + + function onDone(error, result) { + const setImmediate = getSetImmediate() + clearTimeout(timer) + setImmediate(() => observer.disconnect()) + if (error) { + reject(error) + } else { + resolve(result) + } + } + function onMutation() { + try { + const result = callback() + if (!result || (Array.isArray(result) && !result.length)) { + onDone(null, true) + } + // If `callback` returns truthy value, wait for the next mutation or timeout. + } catch (error) { + onDone(null, true) + } + } + function onTimeout() { + onDone(new Error('Timed out in waitForElementToBeRemoved.'), null) + } + }) +} + +export {waitForElementToBeRemoved} diff --git a/typings/wait-for-element-to-be-removed.d.ts b/typings/wait-for-element-to-be-removed.d.ts new file mode 100644 index 00000000..7a353286 --- /dev/null +++ b/typings/wait-for-element-to-be-removed.d.ts @@ -0,0 +1,8 @@ +export function waitForElementToBeRemoved( + callback: () => T, + options?: { + container?: HTMLElement + timeout?: number + mutationObserverOptions?: MutationObserverInit + }, +): Promise