diff --git a/circle.yml b/circle.yml
index 64d06b24..d42e9080 100644
--- a/circle.yml
+++ b/circle.yml
@@ -22,13 +22,27 @@ workflows:
- run:
name: Stop exclusive tests 1️⃣
command: npm run stop-only
- - run:
- name: Check markdown links ⚓️
- command: npm run check:links
- run:
name: Build folder 🏗
command: npm run build
+ - cypress/run:
+ name: Lint Markdown links
+ filters:
+ branches:
+ only: main
+ executor: cypress/base-12
+ requires:
+ - Install
+ # notice a trick to avoid re-installing dependencies
+ # in this job - a do-nothing "install-command" parameter
+ install-command: echo 'Nothing to install in this job'
+ # we are not going to use results from this job anywhere else
+ no-workspace: true
+ record: false
+ store_artifacts: true
+ command: npm run check:links
+
- cypress/run:
name: Example A11y
requires:
@@ -55,6 +69,19 @@ workflows:
command: npm test
store_artifacts: true
+ - cypress/run:
+ name: Example Babel + Typescript
+ requires:
+ - Install
+ executor: cypress/base-12
+ # each example installs "cypress-react-unit-test" as a local dependency (symlink)
+ install-command: npm install
+ verify-command: echo 'Already verified'
+ no-workspace: true
+ working_directory: examples/using-babel-typesceript
+ command: npm test
+ store_artifacts: true
+
- cypress/run:
name: Example React Scripts
requires:
@@ -261,7 +288,7 @@ workflows:
store_artifacts: true
- cypress/run:
- name: Test
+ name: Component Tests
executor: cypress/base-12
parallelism: 4
requires:
@@ -275,6 +302,7 @@ workflows:
store_artifacts: true
# following examples from
# https://circleci.com/docs/2.0/parallelism-faster-jobs/
+ # TODO probably only run component tests and move integration sanity checks into own job
command: |
TESTFILES=$(circleci tests glob "cypress/{component,integration}/**/*spec.{js,jsx,ts,tsx}" | circleci tests split --total=4)
echo "Test files for this machine are $TESTFILES"
@@ -294,7 +322,8 @@ workflows:
executor: cypress/base-12
requires:
- Install
- - Test
+ - Component Tests
+ - Lint Markdown links
- Example A11y
- Example Babel
- Example Component Folder
diff --git a/examples/using-babel-typescript/.npmrc b/examples/using-babel-typescript/.npmrc
new file mode 100644
index 00000000..43c97e71
--- /dev/null
+++ b/examples/using-babel-typescript/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/examples/using-babel-typescript/README.md b/examples/using-babel-typescript/README.md
new file mode 100644
index 00000000..7ca76123
--- /dev/null
+++ b/examples/using-babel-typescript/README.md
@@ -0,0 +1,74 @@
+# example: using-babel
+
+> Component testing for typescript projects using Babel config with `@babel/preset-typescript`
+
+
+
+## Usage
+
+1. Make sure the root project has been built .
+
+```bash
+# in the root of the project
+npm install
+npm run build
+```
+
+2. Run `npm install` in this folder to symlink the `cypress-react-unit-test` dependency.
+
+```bash
+# in this folder
+npm install
+```
+
+3. Start Cypress
+
+```bash
+npm run cy:open
+# or just run headless tests
+npm test
+```
+
+## Specs
+
+See spec files [src/\*.spec.js](src). The specs are bundled using [babel.config.js](babel.config.js) settings via [cypress/plugins/index.js](cypress/plugins/index.js) file that includes file preprocessor.
+
+```js
+// let's bundle spec files and the components they include using
+// the same bundling settings as the project by loading .babelrc
+const preprocessor = require('cypress-react-unit-test/plugins/babelrc')
+module.exports = (on, config) => {
+ preprocessor(on, config)
+ // IMPORTANT to return the config object
+ // with the any changed environment variables
+ return config
+}
+```
+
+## Mocking
+
+During test runs, there is a Babel plugin that transforms ES6 imports into plain objects that can be stubbed using [cy.stub](https://on.cypress.io/stub). In essence
+
+```ts
+// component imports named ES6 import from "calc.js
+import { getRandomNumber } from './calc'
+const Component = () => {
+ // then calls it
+ const n = getRandomNumber()
+ return
{n}
+}
+```
+
+The test can mock that import before mounting the component
+
+```js
+import Component from './Component'
+import * as calc from './calc'
+describe('Component', () => {
+ it('mocks call from the component', () => {
+ cy.stub(calc, 'getRandomNumber')
+ .returns(777)
+ mount()
+ })
+})
+```
diff --git a/examples/using-babel-typescript/babel.config.js b/examples/using-babel-typescript/babel.config.js
new file mode 100644
index 00000000..91ccb7fc
--- /dev/null
+++ b/examples/using-babel-typescript/babel.config.js
@@ -0,0 +1,27 @@
+module.exports = {
+ presets: [
+ '@babel/preset-env',
+ '@babel/preset-typescript',
+ '@babel/preset-react',
+ ],
+ plugins: ['@babel/plugin-proposal-class-properties'],
+ env: {
+ // place plugins for Cypress tests into "test" environment
+ // so that production bundle is not instrumented
+ test: {
+ plugins: [
+ // during Cypress tests we want to instrument source code
+ // to get code coverage from tests
+ 'babel-plugin-istanbul',
+ // we also want to export ES6 modules as objects
+ // to allow mocking named imports
+ [
+ '@babel/plugin-transform-modules-commonjs',
+ {
+ loose: true,
+ },
+ ],
+ ],
+ },
+ },
+}
diff --git a/examples/using-babel-typescript/cypress.json b/examples/using-babel-typescript/cypress.json
new file mode 100644
index 00000000..6d313c40
--- /dev/null
+++ b/examples/using-babel-typescript/cypress.json
@@ -0,0 +1,8 @@
+{
+ "fixturesFolder": false,
+ "testFiles": "**/*spec.tsx",
+ "viewportWidth": 500,
+ "viewportHeight": 500,
+ "experimentalComponentTesting": true,
+ "componentFolder": "src"
+}
diff --git a/examples/using-babel-typescript/cypress/integration/spec.js b/examples/using-babel-typescript/cypress/integration/spec.js
new file mode 100644
index 00000000..d815fd54
--- /dev/null
+++ b/examples/using-babel-typescript/cypress/integration/spec.js
@@ -0,0 +1,6 @@
+///
+describe('integration spec', () => {
+ it('works', () => {
+ expect(1).to.equal(1)
+ })
+})
diff --git a/examples/using-babel-typescript/cypress/plugins/index.js b/examples/using-babel-typescript/cypress/plugins/index.js
new file mode 100644
index 00000000..070a1f84
--- /dev/null
+++ b/examples/using-babel-typescript/cypress/plugins/index.js
@@ -0,0 +1,10 @@
+// let's bundle spec files and the components they include using
+// the same bundling settings as the project by loading .babelrc
+// https://github.com/bahmutov/cypress-react-unit-test#install
+const preprocessor = require('cypress-react-unit-test/plugins/babelrc')
+module.exports = (on, config) => {
+ preprocessor(on, config)
+ // IMPORTANT to return the config object
+ // with the any changed environment variables
+ return config
+}
diff --git a/examples/using-babel-typescript/cypress/support/index.js b/examples/using-babel-typescript/cypress/support/index.js
new file mode 100644
index 00000000..5d9ef5d1
--- /dev/null
+++ b/examples/using-babel-typescript/cypress/support/index.js
@@ -0,0 +1 @@
+require('cypress-react-unit-test/dist/hooks')
diff --git a/examples/using-babel-typescript/images/dynamic.gif b/examples/using-babel-typescript/images/dynamic.gif
new file mode 100644
index 00000000..d37a0fce
Binary files /dev/null and b/examples/using-babel-typescript/images/dynamic.gif differ
diff --git a/examples/using-babel-typescript/package.json b/examples/using-babel-typescript/package.json
new file mode 100644
index 00000000..3604977d
--- /dev/null
+++ b/examples/using-babel-typescript/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "example-using-babel",
+ "description": "Component testing for projects using Babel config",
+ "private": true,
+ "scripts": {
+ "test": "node ../../scripts/cypress-expect run --passing 17",
+ "cy:open": "../../node_modules/.bin/cypress open"
+ },
+ "devDependencies": {
+ "cypress-react-unit-test": "file:../.."
+ }
+}
diff --git a/examples/using-babel-typescript/src/Component.spec.tsx b/examples/using-babel-typescript/src/Component.spec.tsx
new file mode 100644
index 00000000..e10b830e
--- /dev/null
+++ b/examples/using-babel-typescript/src/Component.spec.tsx
@@ -0,0 +1,17 @@
+import * as React from 'react'
+import { Component } from './Component'
+import * as calc from './calc'
+import { mount } from 'cypress-react-unit-test'
+
+// import the component and the file it imports
+// stub the method on the imported "calc" and
+// confirm the component renders the mock value
+describe('Component', () => {
+ it('mocks call from the component', () => {
+ cy.stub(calc, 'getRandomNumber').returns(777)
+
+ mount()
+
+ cy.get('.random').contains('777')
+ })
+})
diff --git a/examples/using-babel-typescript/src/Component.tsx b/examples/using-babel-typescript/src/Component.tsx
new file mode 100644
index 00000000..11c88b5d
--- /dev/null
+++ b/examples/using-babel-typescript/src/Component.tsx
@@ -0,0 +1,14 @@
+import * as React from 'react'
+import { getRandomNumber } from './calc'
+
+/**
+ * Example React component that imports `getRandomNumber`
+ * function from another file and uses it to show a random
+ * number in the UI.
+ */
+export const Component = () => {
+ const n = getRandomNumber()
+ return {n}
+}
+
+export default Component
diff --git a/examples/using-babel-typescript/src/calc.ts b/examples/using-babel-typescript/src/calc.ts
new file mode 100644
index 00000000..fe2bb220
--- /dev/null
+++ b/examples/using-babel-typescript/src/calc.ts
@@ -0,0 +1 @@
+export const getRandomNumber = () => Math.round(Math.random() * 1000)
diff --git a/examples/using-babel-typescript/tsconfig.json b/examples/using-babel-typescript/tsconfig.json
new file mode 100644
index 00000000..d512fb8b
--- /dev/null
+++ b/examples/using-babel-typescript/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "jsx": "react",
+ "baseUrl": ".",
+ "noEmit": true,
+ "target": "esnext",
+ "types": ["cypress"],
+ // ⚠️ required only for demo purposes, remove from code
+ "paths": {
+ "cypress-react-unit-test": ["../../lib/index.ts"]
+ }
+ },
+ "exclude": ["node_modules"],
+ "include": ["./src/**.ts*"]
+}
diff --git a/lib/index.ts b/lib/index.ts
index 7fc96709..5d0e3ff3 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -5,11 +5,9 @@ import { injectStylesBeforeElement } from './utils'
const rootId = 'cypress-root'
-// @ts-ignore
const isComponentSpec = () => Cypress.spec.specType === 'component'
function checkMountModeEnabled() {
- // @ts-ignore
if (!isComponentSpec()) {
throw new Error(
`In order to use mount or unmount functions please place the spec in component folder`,
@@ -68,7 +66,7 @@ export const mount = (jsx: React.ReactElement, options: MountOptions = {}) => {
})
.then(injectStyles(options))
.then(() => {
- const document = cy.state('document')
+ const document = cy.state('document') as Document
const reactDomToUse = options.ReactDom || ReactDOM
const el = document.getElementById(rootId)
@@ -113,7 +111,10 @@ export const mount = (jsx: React.ReactElement, options: MountOptions = {}) => {
logInstance.set('consoleProps', () => logConsoleProps)
if (el.children.length) {
- logInstance.set('$el', el.children.item(0))
+ logInstance.set(
+ '$el',
+ (el.children.item(0) as unknown) as JQuery,
+ )
}
}
@@ -168,8 +169,8 @@ export const unmount = () => {
// mounting hooks inside a test component mostly copied from
// https://github.com/testing-library/react-hooks-testing-library/blob/master/src/pure.js
-function resultContainer() {
- let value: any = null
+function resultContainer() {
+ let value: T | undefined | null = null
let error: Error | null = null
const resolvers: any[] = []
@@ -185,7 +186,7 @@ function resultContainer() {
},
}
- const updateResult = (val: any, err: Error | null = null) => {
+ const updateResult = (val: T | undefined, err: Error | null = null) => {
value = val
error = err
resolvers.splice(0, resolvers.length).forEach(resolve => resolve())
@@ -196,13 +197,18 @@ function resultContainer() {
addResolver: (resolver: any) => {
resolvers.push(resolver)
},
- setValue: (val: any) => updateResult(val),
+ setValue: (val: T) => updateResult(val),
setError: (err: Error) => updateResult(undefined, err),
}
}
-// @ts-ignore
-function TestHook({ callback, onError, children }) {
+type TestHookProps = {
+ callback: () => void
+ onError: (e: Error) => void
+ children: (...args: any[]) => any
+}
+
+function TestHook({ callback, onError, children }: TestHookProps) {
try {
children(callback())
} catch (err) {
@@ -225,7 +231,7 @@ function TestHook({ callback, onError, children }) {
*
* @see https://github.com/bahmutov/cypress-react-unit-test#advanced-examples
*/
-export const mountHook = (hookFn: Function) => {
+export const mountHook = (hookFn: (...args: any[]) => any) => {
const { result, setValue, setError } = resultContainer()
return mount(
diff --git a/plugins/babelrc/file-preprocessor.js b/plugins/babelrc/file-preprocessor.js
index f3688593..97e1ee3e 100644
--- a/plugins/babelrc/file-preprocessor.js
+++ b/plugins/babelrc/file-preprocessor.js
@@ -4,6 +4,16 @@ const debug = require('debug')('cypress-react-unit-test')
const webpackPreprocessor = require('@cypress/webpack-preprocessor')
const { addImageRedirect } = require('../utils/add-image-redirect')
+const wpPreprocessorOptions = {
+ ...webpackPreprocessor.defaultOptions,
+}
+
+wpPreprocessorOptions.webpackOptions.resolve = {
+ extensions: ['.js', '.ts', '.jsx', '.tsx', '.json'],
+}
+
+wpPreprocessorOptions.webpackOptions.module.rules[0].test = /\.(jsx|tsx|js|ts)?$/
+
// note: modifies the input object
function enableBabelrc(webpackOptions) {
if (!Array.isArray(webpackOptions.module.rules)) {
@@ -66,7 +76,6 @@ module.exports = config => {
config && config.env && config.env.coverage === false
debug('coverage is disabled? %o', { coverageIsDisabled })
- const wpPreprocessorOptions = webpackPreprocessor.defaultOptions
enableBabelrc(wpPreprocessorOptions.webpackOptions)
debug('webpack options %o', wpPreprocessorOptions.webpackOptions)