From c78ccf987eb9ab29c47ef78b0cef9e94f746e553 Mon Sep 17 00:00:00 2001 From: BlueWinds Date: Thu, 27 Oct 2022 13:56:53 -0700 Subject: [PATCH 1/7] Use Commands.addQuery rather than Commands.add --- src/add-commands.js | 4 +- src/index.js | 129 ++++++++++++++++---------------------------- src/utils.js | 12 +---- 3 files changed, 50 insertions(+), 95 deletions(-) diff --git a/src/add-commands.js b/src/add-commands.js index 199bb8c..890808a 100644 --- a/src/add-commands.js +++ b/src/add-commands.js @@ -1,7 +1,7 @@ import {configure, commands} from './' -commands.forEach(({name, command, options = {}}) => { - Cypress.Commands.add(name, options, command) +commands.forEach(({name, command}) => { + Cypress.Commands.addQuery(name, command) }) Cypress.Commands.add('configureCypressTestingLibrary', config => { diff --git a/src/index.js b/src/index.js index 1275213..824a1a4 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ import {configure as configureDTL, queries} from '@testing-library/dom' -import {getContainer} from './utils' +import {getFirstElement} from './utils' function configure({fallbackRetryWithoutPreviousSubject, ...config}) { return configureDTL(config) @@ -9,65 +9,78 @@ const findRegex = /^find/ const queryNames = Object.keys(queries).filter(q => findRegex.test(q)) const commands = queryNames.map(queryName => { - return createCommand(queryName, queryName.replace(findRegex, 'get')) + return createQuery(queryName, queryName.replace(findRegex, 'get')) }) -function createCommand(queryName, implementationName) { +function createQuery(queryName, implementationName) { return { name: queryName, - options: {prevSubject: ['optional']}, - command: (prevSubject, ...args) => { + command(...args) { const lastArg = args[args.length - 1] - const defaults = { - timeout: Cypress.config().defaultCommandTimeout, - log: true, - } - const options = - typeof lastArg === 'object' ? {...defaults, ...lastArg} : defaults + const options = typeof lastArg === 'object' ? {...lastArg} : {} - const queryImpl = queries[implementationName] - const baseCommandImpl = container => { - return queryImpl(getContainer(container), ...args) - } - const commandImpl = container => baseCommandImpl(container) + this.set('timeout', options.timeout) + const queryImpl = queries[implementationName] const inputArr = args.filter(filterInputs) - const getSelector = () => `${queryName}(${queryArgument(args)})` - - const win = cy.state('window') + const selector = `${queryName}(${queryArgument(args)})` const consoleProps = { // TODO: Would be good to completely separate out the types of input into their own properties input: inputArr, - Selector: getSelector(), - 'Applied To': getContainer( - options.container || prevSubject || win.document, - ), + Selector: selector, } - if (options.log) { - options._log = Cypress.log({ - type: prevSubject ? 'child' : 'parent', + const log = + options.log !== false && + Cypress.log({ name: queryName, + type: + this.get('prev').get('chainerId') === this.get('chainerId') + ? 'child' + : 'parent', message: inputArr, consoleProps: () => consoleProps, }) - } - const getValue = ( - container = options.container || prevSubject || win.document, - ) => { - const value = commandImpl(container) + const withinSubject = cy.state('withinSubjectChain') + + let error + this.set('onFail', err => { + if (error) { + err.message = error.message + } + }) + + return subject => { + const container = getFirstElement( + options.container || + subject || + cy.getSubjectFromChain(withinSubject) || + cy.state('window').document, + ) + consoleProps['Applied To'] = container + + let value + + try { + value = queryImpl(container, ...args) + } catch (e) { + error = e + value = Cypress.$() + value.selector = selector + } const result = Cypress.$(value) - if (value && options._log) { - options._log.set('$el', result) + + if (value && log) { + log.set('$el', result) } // Overriding the selector of the jquery object because it's displayed in the long message of .should('exist') failure message // Hopefully it makes it clearer, because I find the normal response of "Expected to find element '', but never found it" confusing - result.selector = getSelector() + result.selector = selector consoleProps.elements = result.length if (result.length === 1) { @@ -85,54 +98,6 @@ function createCommand(queryName, implementationName) { return result } - - let error - - // Errors will be thrown by @testing-library/dom, but a query might be followed by `.should('not.exist')` - // We just need to capture the error thrown by @testing-library/dom and return an empty jQuery NodeList - // to allow Cypress assertions errors to happen naturally. If an assertion fails, we'll have a helpful - // error message handy to pass on to the user - const catchQueryError = err => { - error = err - const result = Cypress.$() - result.selector = getSelector() - return result - } - - const resolveValue = () => { - // retry calling "getValue" until following assertions pass or this command times out - return Cypress.Promise.try(getValue) - .catch(catchQueryError) - .then(value => { - return cy.verifyUpcomingAssertions(value, options, { - onRetry: resolveValue, - onFail: () => { - // We want to override Cypress's normal non-existence message with @testing-library/dom's more helpful ones - if (error) { - options.error = error - } - }, - }) - }) - } - - return resolveValue() - .then(subject => { - // Remove the error that occurred because it is irrelevant now - if (consoleProps.error) { - delete consoleProps.error - } - if (options._log) { - options._log.snapshot() - } - - return subject - }) - .finally(() => { - if (options._log) { - options._log.end() - } - }) }, } } diff --git a/src/utils.js b/src/utils.js index 649e11d..dc010c4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -5,16 +5,6 @@ function getFirstElement(jqueryOrElement) { return jqueryOrElement } -function getContainer(container) { - const subject = cy.state('subject') - const withinContainer = cy.state('withinSubject') - - if (!subject && withinContainer) { - return getFirstElement(withinContainer) - } - return getFirstElement(container) -} - -export {getFirstElement, getContainer} +export {getFirstElement} /* globals Cypress, cy */ From 119054b5963b0d2e064b13c5cc6fc9db32c8b7b5 Mon Sep 17 00:00:00 2001 From: BlueWinds Date: Thu, 27 Oct 2022 14:56:43 -0700 Subject: [PATCH 2/7] Add /dist temprorarily to get Cypress tests running while pointing to this branch --- .gitignore | 1 - dist/add-commands.js | 14 ++++++ dist/index.js | 109 +++++++++++++++++++++++++++++++++++++++++++ dist/utils.js | 14 ++++++ 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 dist/add-commands.js create mode 100644 dist/index.js create mode 100644 dist/utils.js diff --git a/.gitignore b/.gitignore index dd6ebe2..20f4331 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ node_modules coverage -dist .DS_Store # these cause more harm than good diff --git a/dist/add-commands.js b/dist/add-commands.js new file mode 100644 index 0000000..cf7698c --- /dev/null +++ b/dist/add-commands.js @@ -0,0 +1,14 @@ +"use strict"; + +var _ = require("./"); +_.commands.forEach(({ + name, + command +}) => { + Cypress.Commands.addQuery(name, command); +}); +Cypress.Commands.add('configureCypressTestingLibrary', config => { + (0, _.configure)(config); +}); + +/* global Cypress */ \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..c06f3fc --- /dev/null +++ b/dist/index.js @@ -0,0 +1,109 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.commands = void 0; +exports.configure = configure; +var _dom = require("@testing-library/dom"); +var _utils = require("./utils"); +function configure({ + fallbackRetryWithoutPreviousSubject, + ...config +}) { + return (0, _dom.configure)(config); +} +const findRegex = /^find/; +const queryNames = Object.keys(_dom.queries).filter(q => findRegex.test(q)); +const commands = queryNames.map(queryName => { + return createQuery(queryName, queryName.replace(findRegex, 'get')); +}); +exports.commands = commands; +function createQuery(queryName, implementationName) { + return { + name: queryName, + command(...args) { + const lastArg = args[args.length - 1]; + const options = typeof lastArg === 'object' ? { + ...lastArg + } : {}; + this.set('timeout', options.timeout); + const queryImpl = _dom.queries[implementationName]; + const inputArr = args.filter(filterInputs); + const selector = `${queryName}(${queryArgument(args)})`; + const consoleProps = { + // TODO: Would be good to completely separate out the types of input into their own properties + input: inputArr, + Selector: selector + }; + const log = options.log !== false && Cypress.log({ + name: queryName, + type: this.get('prev').get('chainerId') === this.get('chainerId') ? 'child' : 'parent', + message: inputArr, + consoleProps: () => consoleProps + }); + const withinSubject = cy.state('withinSubjectChain'); + let error; + this.set('onFail', err => { + if (error) { + err.message = error.message; + } + }); + return subject => { + const container = (0, _utils.getFirstElement)(options.container || subject || cy.getSubjectFromChain(withinSubject) || cy.state('window').document); + consoleProps['Applied To'] = container; + let value; + try { + value = queryImpl(container, ...args); + } catch (e) { + error = e; + value = Cypress.$(); + value.selector = selector; + } + const result = Cypress.$(value); + if (value && log) { + log.set('$el', result); + } + + // Overriding the selector of the jquery object because it's displayed in the long message of .should('exist') failure message + // Hopefully it makes it clearer, because I find the normal response of "Expected to find element '', but never found it" confusing + result.selector = selector; + consoleProps.elements = result.length; + if (result.length === 1) { + consoleProps.yielded = result.toArray()[0]; + } else if (result.length > 0) { + consoleProps.yielded = result.toArray(); + } + if (result.length > 1 && !/All/.test(queryName)) { + // Is this useful? + throw Error(`Found multiple elements with the text: ${queryArgument(args)}`); + } + return result; + }; + } + }; +} +function filterInputs(value) { + if (Array.isArray(value) && value.length === 0) { + return false; + } + if (value instanceof RegExp) { + return value.toString(); + } + if (typeof value === 'object' && Object.keys(value).length === 0) { + return false; + } + return Boolean(value); +} +function queryArgument(args) { + const input = args.find(value => { + return value instanceof RegExp || typeof value === 'string'; + }); + if (input && typeof input === 'string') { + return `\`${input}\``; + } + return input; +} + +/* eslint no-new-func:0, complexity:0 */ +/* globals Cypress, cy */ \ No newline at end of file diff --git a/dist/utils.js b/dist/utils.js new file mode 100644 index 0000000..fca0d7f --- /dev/null +++ b/dist/utils.js @@ -0,0 +1,14 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getFirstElement = getFirstElement; +function getFirstElement(jqueryOrElement) { + if (Cypress.dom.isJquery(jqueryOrElement)) { + return jqueryOrElement.get(0); + } + return jqueryOrElement; +} + +/* globals Cypress, cy */ \ No newline at end of file From 3b2e7252a1ffcff05e65ff4d2f97ce39cb88102b Mon Sep 17 00:00:00 2001 From: BlueWinds Date: Mon, 14 Nov 2022 14:45:29 -0800 Subject: [PATCH 3/7] Revert "Add /dist temprorarily to get Cypress tests running while pointing to this branch" This reverts commit 119054b5963b0d2e064b13c5cc6fc9db32c8b7b5. --- .gitignore | 1 + dist/add-commands.js | 14 ------ dist/index.js | 109 ------------------------------------------- dist/utils.js | 14 ------ 4 files changed, 1 insertion(+), 137 deletions(-) delete mode 100644 dist/add-commands.js delete mode 100644 dist/index.js delete mode 100644 dist/utils.js diff --git a/.gitignore b/.gitignore index 20f4331..dd6ebe2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules coverage +dist .DS_Store # these cause more harm than good diff --git a/dist/add-commands.js b/dist/add-commands.js deleted file mode 100644 index cf7698c..0000000 --- a/dist/add-commands.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; - -var _ = require("./"); -_.commands.forEach(({ - name, - command -}) => { - Cypress.Commands.addQuery(name, command); -}); -Cypress.Commands.add('configureCypressTestingLibrary', config => { - (0, _.configure)(config); -}); - -/* global Cypress */ \ No newline at end of file diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index c06f3fc..0000000 --- a/dist/index.js +++ /dev/null @@ -1,109 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.commands = void 0; -exports.configure = configure; -var _dom = require("@testing-library/dom"); -var _utils = require("./utils"); -function configure({ - fallbackRetryWithoutPreviousSubject, - ...config -}) { - return (0, _dom.configure)(config); -} -const findRegex = /^find/; -const queryNames = Object.keys(_dom.queries).filter(q => findRegex.test(q)); -const commands = queryNames.map(queryName => { - return createQuery(queryName, queryName.replace(findRegex, 'get')); -}); -exports.commands = commands; -function createQuery(queryName, implementationName) { - return { - name: queryName, - command(...args) { - const lastArg = args[args.length - 1]; - const options = typeof lastArg === 'object' ? { - ...lastArg - } : {}; - this.set('timeout', options.timeout); - const queryImpl = _dom.queries[implementationName]; - const inputArr = args.filter(filterInputs); - const selector = `${queryName}(${queryArgument(args)})`; - const consoleProps = { - // TODO: Would be good to completely separate out the types of input into their own properties - input: inputArr, - Selector: selector - }; - const log = options.log !== false && Cypress.log({ - name: queryName, - type: this.get('prev').get('chainerId') === this.get('chainerId') ? 'child' : 'parent', - message: inputArr, - consoleProps: () => consoleProps - }); - const withinSubject = cy.state('withinSubjectChain'); - let error; - this.set('onFail', err => { - if (error) { - err.message = error.message; - } - }); - return subject => { - const container = (0, _utils.getFirstElement)(options.container || subject || cy.getSubjectFromChain(withinSubject) || cy.state('window').document); - consoleProps['Applied To'] = container; - let value; - try { - value = queryImpl(container, ...args); - } catch (e) { - error = e; - value = Cypress.$(); - value.selector = selector; - } - const result = Cypress.$(value); - if (value && log) { - log.set('$el', result); - } - - // Overriding the selector of the jquery object because it's displayed in the long message of .should('exist') failure message - // Hopefully it makes it clearer, because I find the normal response of "Expected to find element '', but never found it" confusing - result.selector = selector; - consoleProps.elements = result.length; - if (result.length === 1) { - consoleProps.yielded = result.toArray()[0]; - } else if (result.length > 0) { - consoleProps.yielded = result.toArray(); - } - if (result.length > 1 && !/All/.test(queryName)) { - // Is this useful? - throw Error(`Found multiple elements with the text: ${queryArgument(args)}`); - } - return result; - }; - } - }; -} -function filterInputs(value) { - if (Array.isArray(value) && value.length === 0) { - return false; - } - if (value instanceof RegExp) { - return value.toString(); - } - if (typeof value === 'object' && Object.keys(value).length === 0) { - return false; - } - return Boolean(value); -} -function queryArgument(args) { - const input = args.find(value => { - return value instanceof RegExp || typeof value === 'string'; - }); - if (input && typeof input === 'string') { - return `\`${input}\``; - } - return input; -} - -/* eslint no-new-func:0, complexity:0 */ -/* globals Cypress, cy */ \ No newline at end of file diff --git a/dist/utils.js b/dist/utils.js deleted file mode 100644 index fca0d7f..0000000 --- a/dist/utils.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getFirstElement = getFirstElement; -function getFirstElement(jqueryOrElement) { - if (Cypress.dom.isJquery(jqueryOrElement)) { - return jqueryOrElement.get(0); - } - return jqueryOrElement; -} - -/* globals Cypress, cy */ \ No newline at end of file From 56ada0e9b5d5d71e74ee09a1bed91e6a3d0f7079 Mon Sep 17 00:00:00 2001 From: BlueWinds Date: Mon, 14 Nov 2022 15:15:01 -0800 Subject: [PATCH 4/7] Mock new API for tests, install pre-release Cy12 binary --- cypress.config.js | 1 + package.json | 2 +- src/__tests__/add-commands.js | 9 +++++---- src/utils.js | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cypress.config.js b/cypress.config.js index d759e85..1a3d306 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,5 +1,6 @@ const {defineConfig} = require('cypress') module.exports = defineConfig({ + e2e: {}, video: false, }) diff --git a/package.json b/package.json index 934e4da..9299e25 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@testing-library/dom": "^8.1.0" }, "devDependencies": { - "cypress": "^10.0.0", + "cypress": "https://cdn.cypress.io/beta/npm/12.0.0/linux-x64/release/12.0.0-cc63b13085591b70826c909119784e7d358ea56e/cypress.tgz", "kcd-scripts": "^11.2.0", "npm-run-all": "^4.1.5", "typescript": "^4.3.5" diff --git a/src/__tests__/add-commands.js b/src/__tests__/add-commands.js index 3d0f9ec..a1ddf20 100644 --- a/src/__tests__/add-commands.js +++ b/src/__tests__/add-commands.js @@ -2,16 +2,17 @@ import {commands} from '../' test('adds commands to Cypress', () => { const addMock = jest.fn().mockName('Cypress.Commands.add') - global.Cypress = {Commands: {add: addMock}} + const addQueryMock = jest.fn().mockName('Cypress.Commands.addQuery') + global.Cypress = {Commands: {add: addMock, addQuery: addQueryMock}} global.cy = {} require('../add-commands') - expect(addMock).toHaveBeenCalledTimes(commands.length + 1) // we're also adding a configuration command + expect(addQueryMock).toHaveBeenCalledTimes(commands.length) + expect(addMock).toHaveBeenCalledTimes(1) // we're also adding a configuration command commands.forEach(({name}, index) => { - expect(addMock.mock.calls[index]).toMatchObject([ + expect(addQueryMock.mock.calls[index]).toMatchObject([ name, - {}, // We get a new function that is `command.bind(null, cy)` i.e. global `cy` passed into the first argument. // The commands themselves will be tested separately in the Cypress end-to-end tests. expect.any(Function), diff --git a/src/utils.js b/src/utils.js index dc010c4..d9db357 100644 --- a/src/utils.js +++ b/src/utils.js @@ -7,4 +7,4 @@ function getFirstElement(jqueryOrElement) { export {getFirstElement} -/* globals Cypress, cy */ +/* globals Cypress */ From 3be2a9c778efc4cddfeef17f6dd4213e697214aa Mon Sep 17 00:00:00 2001 From: BlueWinds Date: Tue, 15 Nov 2022 07:54:16 -0800 Subject: [PATCH 5/7] Update pre-release binary to fixed version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9299e25..09b6515 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@testing-library/dom": "^8.1.0" }, "devDependencies": { - "cypress": "https://cdn.cypress.io/beta/npm/12.0.0/linux-x64/release/12.0.0-cc63b13085591b70826c909119784e7d358ea56e/cypress.tgz", + "cypress": "https://cdn.cypress.io/beta/npm/12.0.0/linux-x64/release/12.0.0-4db85d690e8439ef4d2397238ab069d557b2e818/cypress.tgz", "kcd-scripts": "^11.2.0", "npm-run-all": "^4.1.5", "typescript": "^4.3.5" From a09d072449b96e8981f66f7e6a2fb9225a80168d Mon Sep 17 00:00:00 2001 From: BlueWinds Date: Mon, 21 Nov 2022 09:51:30 -0800 Subject: [PATCH 6/7] BREAKING CHANGE: Use addQuery interface, which is only present in Cypress 12+. Set Cypress peerDependency to ^12.0.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6933014..60ed85f 100644 --- a/package.json +++ b/package.json @@ -44,13 +44,13 @@ "@testing-library/dom": "^8.1.0" }, "devDependencies": { - "cypress": "https://cdn.cypress.io/beta/npm/12.0.0/linux-x64/release/12.0.0-4db85d690e8439ef4d2397238ab069d557b2e818/cypress.tgz", + "cypress": "https://cdn.cypress.io/beta/npm/12.0.0/linux-x64/release/12.0.0-79ea4537649ec49b72286fc6ba48089a0b9ff879/cypress.tgz", "kcd-scripts": "^11.2.0", "npm-run-all": "^4.1.5", "typescript": "^4.3.5" }, "peerDependencies": { - "cypress": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + "cypress": "^12.0.0" }, "eslintConfig": { "extends": "./node_modules/kcd-scripts/eslint.js", From c431d0488e27fb425661e4520bc8c3c36fa62d5d Mon Sep 17 00:00:00 2001 From: BlueWinds Date: Tue, 6 Dec 2022 09:28:46 -0800 Subject: [PATCH 7/7] Update to released version of Cypress 12.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 60ed85f..3f9703f 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@testing-library/dom": "^8.1.0" }, "devDependencies": { - "cypress": "https://cdn.cypress.io/beta/npm/12.0.0/linux-x64/release/12.0.0-79ea4537649ec49b72286fc6ba48089a0b9ff879/cypress.tgz", + "cypress": "^12.0.0", "kcd-scripts": "^11.2.0", "npm-run-all": "^4.1.5", "typescript": "^4.3.5"