mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-06 23:10:22 -05:00
chore: cutting over system-tests and Cypress to use the new CT Object API (#21079)
* removing vite-dev-server local dependency from react-vite-ts-configured system test * moving some CRA examples over to use the object api for setup * fixing issue where function API was broken by object API for cy config + devservers * adding deeply nested react import to project-fixtures for cra * finishes cutting over cypress/react for sys tests * chore: adding circle for this feature branch * chore: moving over many vue + vite system tests to use object API instead of function API (#21080) * doing webpack-dev-server cutovers * removing more webpack-dev-server refrences * fixing snapshots * bumping yarn.lock * wip * fix test * fix assertion Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com> * feat: removing all references for "fresh" dev servers (webpack-dev-server-fresh and vite-dev-server-fresh) (#21094) Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com> Co-authored-by: Zachary Williams <ZachJW34@gmail.com> * chore: add dev-servers as deps to server to be included in the binary (#21091) * fix bad merge * fix next types and webpack-dev-server- resolve Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com> Co-authored-by: Zachary Williams <ZachJW34@gmail.com>
This commit is contained in:
+2
-2
@@ -80,8 +80,8 @@ system-tests/lib/fixtureDirs.ts
|
||||
/npm/design-system/cypress/videos
|
||||
/npm/design-system/.babel-cache
|
||||
|
||||
# from npm/webpack-dev-server-fresh
|
||||
/npm/webpack-dev-server-fresh/cypress/videos
|
||||
# from npm/webpack-dev-server
|
||||
/npm/webpack-dev-server/cypress/videos
|
||||
|
||||
# from runner-ct
|
||||
/packages/runner-ct/cypress/screenshots
|
||||
|
||||
+21
-29
@@ -29,7 +29,7 @@ mainBuildFilters: &mainBuildFilters
|
||||
only:
|
||||
- develop
|
||||
- 10.0-release
|
||||
- zachw/add-dev-server-deps
|
||||
- chore/cutover-to-bundled-react-mount
|
||||
|
||||
# uncomment & add to the branch conditions below to disable the main linux
|
||||
# flow if we don't want to test it for a certain branch
|
||||
@@ -47,7 +47,7 @@ macWorkflowFilters: &mac-workflow-filters
|
||||
or:
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
- equal: [ '10.0-release', << pipeline.git.branch >> ]
|
||||
- equal: [ zachw/add-dev-server-deps, << pipeline.git.branch >> ]
|
||||
- equal: [ chore/cutover-to-bundled-react-mount, << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: "-release$"
|
||||
value: << pipeline.git.branch >>
|
||||
@@ -57,7 +57,8 @@ windowsWorkflowFilters: &windows-workflow-filters
|
||||
or:
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
- equal: [ '10.0-release', << pipeline.git.branch >> ]
|
||||
- equal: [ zachw/add-dev-server-deps, << pipeline.git.branch >> ]
|
||||
- equal: [ chore/cutover-to-bundled-react-mount, << pipeline.git.branch >> ]
|
||||
- equal: [ 'unify-1036-windows-test-projects', << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: "-release$"
|
||||
value: << pipeline.git.branch >>
|
||||
@@ -1101,8 +1102,8 @@ jobs:
|
||||
run-frontend-shared-component-tests-chrome,
|
||||
run-launchpad-component-tests-chrome,
|
||||
run-launchpad-integration-tests-chrome,
|
||||
run-webpack-dev-server-fresh-integration-tests,
|
||||
run-vite-dev-server-fresh-integration-tests
|
||||
run-webpack-dev-server-integration-tests,
|
||||
run-vite-dev-server-integration-tests
|
||||
- run:
|
||||
command: |
|
||||
PERCY_PARALLEL_NONCE=$CIRCLE_SHA1 \
|
||||
@@ -1161,8 +1162,10 @@ jobs:
|
||||
- run: yarn test-scripts
|
||||
# make sure our snapshots are compared correctly
|
||||
- run: yarn test-mocha-snapshot
|
||||
# run @cypress/design-system before other packages are built
|
||||
- run: yarn lerna run build-prod --scope \"@cypress/design-system\"
|
||||
# make sure packages with TypeScript can be transpiled to JS
|
||||
- run: yarn lerna run build-prod --stream
|
||||
- run: yarn lerna run build-prod --stream --ignore \"@cypress/design-system\"
|
||||
# run unit tests from each individual package
|
||||
- run: yarn test
|
||||
# run type checking for each individual package
|
||||
@@ -1437,7 +1440,7 @@ jobs:
|
||||
path: /tmp/artifacts
|
||||
- store-npm-logs
|
||||
|
||||
run-webpack-dev-server-fresh-integration-tests:
|
||||
run-webpack-dev-server-integration-tests:
|
||||
<<: *defaults
|
||||
# parallelism: 3 TODO: Add parallelism once we have more specs
|
||||
steps:
|
||||
@@ -1451,15 +1454,15 @@ jobs:
|
||||
PERCY_ENABLE=${PERCY_TOKEN:-0} \
|
||||
PERCY_PARALLEL_TOTAL=-1 \
|
||||
yarn percy exec --parallel -- -- \
|
||||
yarn cypress:run --record --parallel --group webpack-dev-server-fresh
|
||||
working_directory: npm/webpack-dev-server-fresh
|
||||
yarn cypress:run --record --parallel --group webpack-dev-server
|
||||
working_directory: npm/webpack-dev-server
|
||||
- store_test_results:
|
||||
path: /tmp/cypress
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts
|
||||
- store-npm-logs
|
||||
|
||||
run-vite-dev-server-fresh-integration-tests:
|
||||
run-vite-dev-server-integration-tests:
|
||||
<<: *defaults
|
||||
# parallelism: 3 TODO: Add parallelism once we have more specs
|
||||
steps:
|
||||
@@ -1473,8 +1476,8 @@ jobs:
|
||||
PERCY_ENABLE=${PERCY_TOKEN:-0} \
|
||||
PERCY_PARALLEL_TOTAL=-1 \
|
||||
yarn percy exec --parallel -- -- \
|
||||
yarn cypress:run --record --parallel --group vite-dev-server-fresh
|
||||
working_directory: npm/vite-dev-server-fresh
|
||||
yarn cypress:run --record --parallel --group vite-dev-server
|
||||
working_directory: npm/vite-dev-server
|
||||
- store_test_results:
|
||||
path: /tmp/cypress
|
||||
- store_artifacts:
|
||||
@@ -1548,7 +1551,7 @@ jobs:
|
||||
command: yarn workspace @cypress/webpack-dev-server test
|
||||
- run:
|
||||
name: Run tests
|
||||
command: yarn workspace @cypress/webpack-dev-server-fresh test
|
||||
command: yarn workspace @cypress/webpack-dev-server test
|
||||
|
||||
npm-vite-dev-server:
|
||||
<<: *defaults
|
||||
@@ -1556,7 +1559,7 @@ jobs:
|
||||
- restore_cached_workspace
|
||||
- run:
|
||||
name: Run tests
|
||||
command: yarn test --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json
|
||||
command: yarn test
|
||||
working_directory: npm/vite-dev-server
|
||||
- store_test_results:
|
||||
path: npm/vite-dev-server/test_results
|
||||
@@ -1582,14 +1585,6 @@ jobs:
|
||||
name: Type Check
|
||||
command: yarn typecheck
|
||||
working_directory: npm/vue
|
||||
- run:
|
||||
name: Run component tests
|
||||
command: yarn test:ci:ct
|
||||
working_directory: npm/vue
|
||||
- run:
|
||||
name: Run e2e tests
|
||||
command: yarn test:ci:e2e
|
||||
working_directory: npm/vue
|
||||
- store_test_results:
|
||||
path: npm/vue/test_results
|
||||
- store_artifacts:
|
||||
@@ -1645,7 +1640,7 @@ jobs:
|
||||
command: yarn workspace @cypress/react build
|
||||
- run:
|
||||
name: Run tests
|
||||
command: yarn test-ci
|
||||
command: yarn test
|
||||
working_directory: npm/react
|
||||
- store_test_results:
|
||||
path: npm/react/test_results
|
||||
@@ -1668,9 +1663,6 @@ jobs:
|
||||
steps:
|
||||
- restore_cached_workspace
|
||||
- run: yarn workspace create-cypress-tests build
|
||||
- run:
|
||||
name: Run unit test
|
||||
command: yarn workspace create-cypress-tests test
|
||||
|
||||
npm-eslint-plugin-dev:
|
||||
<<: *defaults
|
||||
@@ -1727,7 +1719,7 @@ jobs:
|
||||
- run:
|
||||
name: Check current branch to persist artifacts
|
||||
command: |
|
||||
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "zachw/add-dev-server-deps" && "$CIRCLE_BRANCH" != "10.0-release" ]]; then
|
||||
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "chore/cutover-to-bundled-react-mount" && "$CIRCLE_BRANCH" != "unify-1036-windows-test-projects" && "$CIRCLE_BRANCH" != "10.0-release" ]]; then
|
||||
echo "Not uploading artifacts or posting install comment for this branch."
|
||||
circleci-agent step halt
|
||||
fi
|
||||
@@ -2270,11 +2262,11 @@ linux-workflow: &linux-workflow
|
||||
percy: true
|
||||
requires:
|
||||
- build
|
||||
- run-webpack-dev-server-fresh-integration-tests:
|
||||
- run-webpack-dev-server-integration-tests:
|
||||
context: [test-runner:cypress-record-key, test-runner:percy]
|
||||
requires:
|
||||
- system-tests-node-modules-install
|
||||
- run-vite-dev-server-fresh-integration-tests:
|
||||
- run-vite-dev-server-integration-tests:
|
||||
context: [test-runner:cypress-record-key, test-runner:percy]
|
||||
requires:
|
||||
- system-tests-node-modules-install
|
||||
|
||||
+12
-12
@@ -6,7 +6,7 @@ NOTE: this is not published on npm yet. It's a work in progress. Consider [Cypre
|
||||
](https://github.com/jscutlery/test-utils/tree/main/packages/cypress-angular) by [JS Cutlery](https://github.com/jscutlery) for a version that's currently working and available on npm.
|
||||
|
||||
```shell
|
||||
npm install -D cypress @cypress/angular @cypress/webpack-dev-server
|
||||
npm install -D cypress @cypress/angular
|
||||
```
|
||||
|
||||
Ensure you have a version of Cypress > 7.
|
||||
@@ -32,21 +32,21 @@ module.exports = {
|
||||
}
|
||||
```
|
||||
|
||||
Configure `cypress/plugins/index.js` to transpile Angular code.
|
||||
Configure `cypress.config.js` to transpile Angular code.
|
||||
|
||||
```javascript
|
||||
import { defineConfig } from 'cypress'
|
||||
import * as webpackConfig from './webpack.config';
|
||||
const { startDevServer } = require('@cypress/webpack-dev-server');
|
||||
|
||||
module.exports = (on, config) => {
|
||||
on('dev-server:start', (options) =>
|
||||
startDevServer({
|
||||
options,
|
||||
webpackConfig,
|
||||
}),
|
||||
);
|
||||
return config;
|
||||
};
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer: {
|
||||
bundler: 'webpack',
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
The `webpack.config.ts` file is [here](cypress/plugins/webpack.config.ts).
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/webpack-dev-server'
|
||||
import * as webpackConfig from './cypress/plugins/webpack.config'
|
||||
|
||||
export default defineConfig({
|
||||
@@ -12,7 +11,11 @@ export default defineConfig({
|
||||
setupNodeEvents (on, config) {
|
||||
return require('./cypress/plugins')(on, config)
|
||||
},
|
||||
devServer,
|
||||
devServerConfig: { webpackConfig },
|
||||
// @ts-ignore TODO: need to add the ability to define framework not
|
||||
// in list w/o types failing...
|
||||
devServer: {
|
||||
bundler: 'webpack',
|
||||
webpackConfig,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -97,8 +97,8 @@ module.exports = {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'test'),
|
||||
}),
|
||||
new webpack.ContextReplacementPlugin(
|
||||
/\@angular(\\|\/)core(\\|\/)f?esm5/,
|
||||
path.join(__dirname, './src'),
|
||||
/angular(\\|\/)core(\\|\/)(@angular|fesm2015)/,
|
||||
path.join(__dirname, '..', '..', 'src'),
|
||||
),
|
||||
],
|
||||
performance: {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"app-start": "ng serve",
|
||||
"app-build": "ng build",
|
||||
"test": "yarn cy:run",
|
||||
"test-ci": "yarn cy:run",
|
||||
"test-ci": "echo not testing in CI because process never exits",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -38,7 +38,6 @@
|
||||
"@angular/platform-browser-dynamic": "11.2.13",
|
||||
"@angular/router": "11.2.13",
|
||||
"@cypress/code-coverage": "3.9.5",
|
||||
"@cypress/webpack-dev-server": "0.0.0-development",
|
||||
"@cypress/webpack-preprocessor": "5.7.0",
|
||||
"@types/cypress-image-snapshot": "3.1.5",
|
||||
"@types/node": "8.10.66",
|
||||
@@ -49,7 +48,6 @@
|
||||
"cypress": "0.0.0-development",
|
||||
"cypress-image-snapshot": "4.0.1",
|
||||
"html-loader": "2.1.2",
|
||||
"html-webpack-plugin": "5.3.1",
|
||||
"istanbul-instrumenter-loader": "3.0.1",
|
||||
"ng-inline-svg": "12.1.0",
|
||||
"ngx-build-plus": "11.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { devServer } = require('@cypress/vite-dev-server')
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = {
|
||||
module.exports = defineConfig({
|
||||
viewportWidth: 1024,
|
||||
viewportHeight: 800,
|
||||
video: false,
|
||||
@@ -14,6 +14,9 @@ module.exports = {
|
||||
'**/__snapshots__/*',
|
||||
'**/__image_snapshots__/*',
|
||||
],
|
||||
devServer,
|
||||
devServer: {
|
||||
framework: 'react',
|
||||
bundler: 'vite',
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"@babel/preset-env": "7.4.5",
|
||||
"@babel/preset-react": "7.0.0",
|
||||
"@babel/preset-typescript": "7.10.4",
|
||||
"@cypress/react": "0.0.0-development",
|
||||
"@cypress/vite-dev-server": "0.0.0-development",
|
||||
"@packages/web-config": "0.0.0-development",
|
||||
"@react-types/button": "^3.3.1",
|
||||
"@react-types/shared": "^3.5.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { mount } from 'cypress/react'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons'
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { CypressLogo } from './CypressLogo/CypressLogo'
|
||||
import { mount } from '@cypress/react'
|
||||
import { mount } from 'cypress/react'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { mount } from 'cypress/react'
|
||||
import { composeStories } from '@storybook/testing-react'
|
||||
import * as stories from './CollapsibleGroup.stories'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { mount } from 'cypress/react'
|
||||
import { FileTree } from './FileTree'
|
||||
import { mountAndSnapshot } from 'util/testing'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { mount } from 'cypress/react'
|
||||
|
||||
import { SearchInput } from './SearchInput'
|
||||
import { mountAndSnapshot } from 'util/testing'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mount } from '@cypress/react'
|
||||
import { mount } from 'cypress/react'
|
||||
import React from 'react'
|
||||
|
||||
export const mountAndSnapshot =
|
||||
|
||||
@@ -239,7 +239,6 @@ Repo | Description
|
||||
[bahmutov/integration-tests](https://github.com/bahmutov/integration-tests) | Example based on blog post [React Integration Testing: Greater Coverage, Fewer Tests](https://css-tricks.com/react-integration-testing-greater-coverage-fewer-tests/)
|
||||
[mobx-react-typescript-boilerplate](https://github.com/bahmutov/mobx-react-typescript-boilerplate) | Fork of the official Mobx example, shows clock control
|
||||
[bahmutov/test-react-hook-form](https://github.com/bahmutov/test-react-hook-form) | Testing forms created using [react-hook-form](https://github.com/react-hook-form/react-hook-form)
|
||||
[bahmutov/react-with-rollup](https://github.com/bahmutov/react-with-rollup) | Testing a React application bundled with Rollup by using [@bahmutov/cy-rollup](https://github.com/bahmutov/cy-rollup) preprocessor
|
||||
[bahmutov/testing-react-example](https://github.com/bahmutov/testing-react-example) | Described in blog post [Test React Component with @cypress/react Example](https://dev.to/bahmutov/test-react-component-with-@cypress/react-example-4d99)
|
||||
[ejected-react-scripts-example](https://github.com/bahmutov/ejected-react-scripts-example) | Using component testing after ejecting `react-scripts`
|
||||
[tic-tac-toe](https://github.com/bahmutov/react-tic-tac-toe-example) | Component and unit tests for Tic-Tac-Toe, read [Tic-Tac-Toe Component Tests](https://glebbahmutov.com/blog/tic-tac-toe-component-tests/)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// @ts-check
|
||||
const { devServer } = require('@cypress/webpack-dev-server')
|
||||
|
||||
module.exports = {
|
||||
'viewportWidth': 400,
|
||||
'viewportHeight': 400,
|
||||
@@ -16,64 +13,9 @@ module.exports = {
|
||||
'**/__image_snapshots__/*',
|
||||
'examples/**/*',
|
||||
],
|
||||
devServer (cypressDevServerConfig, devServerConfig) {
|
||||
const path = require('path')
|
||||
const babelConfig = require('./babel.config.js')
|
||||
|
||||
const webpackConfig = {
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
||||
},
|
||||
mode: 'development',
|
||||
devtool: false,
|
||||
output: {
|
||||
publicPath: '/',
|
||||
chunkFilename: '[name].bundle.js',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx|mjs|ts|tsx)$/,
|
||||
loader: 'babel-loader',
|
||||
options: { ...babelConfig, cacheDirectory: path.resolve(__dirname, '..', '..', '.babel-cache') },
|
||||
},
|
||||
{
|
||||
test: /\.modules\.css$/i,
|
||||
exclude: [/node_modules/],
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: [/node_modules/, /\.modules\.css$/i],
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
{
|
||||
// some of our examples import SVG
|
||||
test: /\.svg$/,
|
||||
loader: 'svg-url-loader',
|
||||
},
|
||||
{
|
||||
// some of our examples import SVG
|
||||
test: /\.svg$/,
|
||||
loader: 'svg-url-loader',
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg)$/,
|
||||
use: ['file-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
return devServer(cypressDevServerConfig, { webpackConfig })
|
||||
devServer: {
|
||||
framework: 'react',
|
||||
bundler: 'vite',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# cy-api test example
|
||||
|
||||
Using component tests with [cy-api](https://github.com/bahmutov/cy-api) tests. Read [Black box API testing with server logs](https://glebbahmutov.com/blog/api-testing-with-sever-logs/)
|
||||
|
||||

|
||||
|
||||
Note: currently the API and component tests do not clear the HTML created by each other
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 326 KiB |
@@ -1,53 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
/// <reference types="../../lib" />
|
||||
/// <reference types="@bahmutov/cy-api" />
|
||||
import { Users } from './users.jsx'
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
// adds cy.api command
|
||||
import '@bahmutov/cy-api/support'
|
||||
|
||||
// mixes component and API tests
|
||||
describe('Component and API tests', () => {
|
||||
it('fetches and shows 3 users', () => {
|
||||
// no mocking, just real request to the backend REST endpoint
|
||||
mount(<Users />)
|
||||
// fetching users can take a while
|
||||
cy.get('li', { timeout: 20000 }).should('have.length', 3)
|
||||
})
|
||||
|
||||
it('checks if API responds with a list of users', () => {
|
||||
cy.api({
|
||||
// same or similar URL to the one the component is using
|
||||
url: 'https://jsonplaceholder.cypress.io/users?_limit=3',
|
||||
})
|
||||
.its('body')
|
||||
.should('have.length', 3)
|
||||
})
|
||||
|
||||
// another component test
|
||||
it('shows stubbed users', () => {
|
||||
cy.stub(window, 'fetch').resolves({
|
||||
json: cy
|
||||
.stub()
|
||||
.resolves([{ id: 101, name: 'Test User' }])
|
||||
.as('users'),
|
||||
})
|
||||
|
||||
// no mocking, just real request to the backend REST endpoint
|
||||
mount(<Users />)
|
||||
cy.get('li').should('have.length', 1)
|
||||
cy.get('@users').should('have.been.calledOnce')
|
||||
})
|
||||
|
||||
it('fetches one user from API', () => {
|
||||
cy.api({
|
||||
url: 'https://jsonplaceholder.cypress.io/users/1',
|
||||
})
|
||||
.its('body')
|
||||
.should('include', {
|
||||
id: 1,
|
||||
name: 'Leanne Graham',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export class Users extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
users: [],
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
fetch('https://jsonplaceholder.cypress.io/users?_limit=3')
|
||||
.then((response) => {
|
||||
return response.json()
|
||||
})
|
||||
.then((list) => {
|
||||
this.setState({
|
||||
users: list,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
{this.state.users.map((user) => (
|
||||
<li key={user.id}>
|
||||
<strong>{user.id}</strong> - {user.name}
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: linear-gradient(180deg, #d309e1 0%, rgb(156, 26, 255) 100%);
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import { Motion } from './Motion'
|
||||
import { mount } from '@cypress/react'
|
||||
|
||||
describe('framer-motion', () => {
|
||||
it('Renders component and retries the animation', () => {
|
||||
mount(<Motion />)
|
||||
|
||||
cy.get('[data-testid=\'motion\']').should('have.css', 'border-radius', '50%')
|
||||
cy.get('[data-testid=\'motion\']').should('have.css', 'border-radius', '20%')
|
||||
})
|
||||
|
||||
// NOTE: looks like cy.tick issue. Refer to the https://github.com/bahmutov/cypress-react-unit-test/issues/420
|
||||
it.skip('Mocks setTimeout and requestAnimationFrame', () => {
|
||||
cy.clock()
|
||||
mount(<Motion />)
|
||||
|
||||
// CI is slow, so check only the approximate values
|
||||
cy.tick(800)
|
||||
cy.get('[data-testid=\'motion\']').within((element) => {
|
||||
expect(parseInt(element.css('borderRadius'))).to.equal(43)
|
||||
})
|
||||
|
||||
cy.tick(100)
|
||||
cy.get('[data-testid=\'motion\']').within((element) => {
|
||||
expect(parseInt(element.css('borderRadius'))).to.equal(48)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,24 +0,0 @@
|
||||
import './Motion.css'
|
||||
import * as React from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
export const Motion = () => {
|
||||
return (
|
||||
<motion.div
|
||||
data-testid="motion"
|
||||
style={{ width: 100, height: 100, backgroundColor: 'white' }}
|
||||
animate={{
|
||||
scale: [1, 2, 2, 1, 1],
|
||||
rotate: [0, 0, 270, 270, 0],
|
||||
borderRadius: ['20%', '20%', '50%', '50%', '20%'],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
ease: 'easeInOut',
|
||||
times: [0, 0.2, 0.5, 0.8, 1],
|
||||
loop: false,
|
||||
repeatDelay: 1,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import { I18nextProvider } from 'react-i18next'
|
||||
import i18n from './i18n'
|
||||
import { LocalizedComponent } from './LocalizedComponent'
|
||||
|
||||
export function App () {
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<LocalizedComponent count={15} name="SomeUserName" />
|
||||
</I18nextProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
interface LocalizedComponentProps {
|
||||
name: string
|
||||
count: number
|
||||
}
|
||||
|
||||
// See ./App.tsx for localization setup
|
||||
export function LocalizedComponent ({ name, count }: LocalizedComponentProps) {
|
||||
return (
|
||||
<Trans
|
||||
i18nKey={count === 1 ? 'userMessagesUnread' : 'userMessagesUnread_plural'}
|
||||
count={count}
|
||||
>
|
||||
Hello <strong> {{ name }} </strong>, you have {{ count }} unread message{' '}
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
## Localization Example
|
||||
|
||||
This example uses [react-i18next](https://react.i18next.com/) for app localization. Make sure that in "real life" application locale related setup performs at the root of application ([App.tsx](./App.tsx)) and the components are using context for localization.
|
||||
|
||||
Thats why in tests we also need to wrap our component with the same provider as our application. Using function composition we can create our own `mount` function which wraps the component with all required providers:
|
||||
|
||||
```js
|
||||
const localizedMount = (node, { locale }) => {
|
||||
mount(
|
||||
<I18nextProvider i18n={i18n.cloneInstance({ lng: locale })}>
|
||||
{node}
|
||||
</I18nextProvider>,
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
i18n
|
||||
.use(initReactI18next) // passes i18n down to react-i18next
|
||||
.init({
|
||||
resources: {
|
||||
en: {
|
||||
translation: {
|
||||
userMessagesUnread:
|
||||
'Hello <1>{{name}}</1>, you have {{count}} unread message.',
|
||||
userMessagesUnread_plural:
|
||||
'Hello <1>{{name}}</1>, you have {{count}} unread messages.',
|
||||
},
|
||||
},
|
||||
ru: {
|
||||
translation: {
|
||||
userMessagesUnread:
|
||||
'Привет, <1>{{name}}</1>, y тебя {{count}} непрочитанное сообщение.',
|
||||
userMessagesUnread_plural:
|
||||
'Привет, <1>{{name}}</1>, y тебя {{count}} непрочитанных сообщений.',
|
||||
},
|
||||
},
|
||||
},
|
||||
lng: 'en',
|
||||
fallbackLng: 'en',
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
})
|
||||
|
||||
export default i18n
|
||||
@@ -1,48 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import * as React from 'react'
|
||||
import i18n from './i18n'
|
||||
import { LocalizedComponent } from './LocalizedComponent'
|
||||
import { mount } from '@cypress/react'
|
||||
import { I18nextProvider } from 'react-i18next'
|
||||
|
||||
describe('i18n', () => {
|
||||
const localizedMount = (node, { locale }) => {
|
||||
mount(
|
||||
<I18nextProvider i18n={i18n.cloneInstance({ lng: locale })}>
|
||||
{node}
|
||||
</I18nextProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
it('Plural in en', () => {
|
||||
localizedMount(<LocalizedComponent count={15} name="Josh" />, {
|
||||
locale: 'en',
|
||||
})
|
||||
|
||||
cy.contains('Hello Josh, you have 15 unread messages.')
|
||||
})
|
||||
|
||||
it('Single in en', () => {
|
||||
localizedMount(<LocalizedComponent count={1} name="Josh" />, {
|
||||
locale: 'en',
|
||||
})
|
||||
|
||||
cy.contains('Hello Josh, you have 1 unread message.')
|
||||
})
|
||||
|
||||
it('Plural in ru', () => {
|
||||
localizedMount(<LocalizedComponent count={15} name="Костя" />, {
|
||||
locale: 'ru',
|
||||
})
|
||||
|
||||
cy.contains('Привет, Костя, y тебя 15 непрочитанных сообщений.')
|
||||
})
|
||||
|
||||
it('Single in ru', () => {
|
||||
localizedMount(<LocalizedComponent count={1} name="Костя" />, {
|
||||
locale: 'ru',
|
||||
})
|
||||
|
||||
cy.contains('Привет, Костя, y тебя 1 непрочитанное сообщение.')
|
||||
})
|
||||
})
|
||||
@@ -1 +0,0 @@
|
||||
Examples from https://material-ui.com/
|
||||
@@ -1,58 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import { mount } from '@cypress/react'
|
||||
// select example from
|
||||
// https://material-ui.com/components/autocomplete/
|
||||
|
||||
/* eslint-disable no-use-before-define */
|
||||
import React from 'react'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete'
|
||||
import { top100Films } from './top-100-movies'
|
||||
|
||||
export default function ComboBox () {
|
||||
return (
|
||||
<Autocomplete
|
||||
id="combo-box-demo"
|
||||
options={top100Films}
|
||||
getOptionLabel={(option) => option.title}
|
||||
style={{ width: 300 }}
|
||||
renderInput={(params) => {
|
||||
return (
|
||||
<TextField {...params} label="Combo box" variant="outlined" fullWidth />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
it('finds my favorite movie', () => {
|
||||
cy.viewport(500, 700)
|
||||
mount(
|
||||
<Autocomplete
|
||||
id="combo-box-demo"
|
||||
options={top100Films}
|
||||
getOptionLabel={(option) => option.title}
|
||||
style={{ width: 300 }}
|
||||
renderInput={(params) => {
|
||||
return (
|
||||
<TextField {...params} label="Combo box" variant="outlined" fullWidth />
|
||||
)
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
stylesheets: [
|
||||
'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap',
|
||||
'https://fonts.googleapis.com/icon?family=Material+Icons',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
cy.get('#combo-box-demo').click()
|
||||
cy.focused().type('god')
|
||||
cy.contains('The Godfather')
|
||||
.should('be.visible')
|
||||
.and('have.class', 'MuiAutocomplete-option')
|
||||
.click()
|
||||
|
||||
cy.get('#combo-box-demo').should('have.value', 'The Godfather')
|
||||
})
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import Button from '@material-ui/core/Button'
|
||||
|
||||
it('renders a button', () => {
|
||||
mount(
|
||||
<Button variant="contained" color="primary">
|
||||
Hello World
|
||||
</Button>,
|
||||
)
|
||||
})
|
||||
|
||||
it('renders a button with an icon', () => {
|
||||
mount(
|
||||
<Button variant="contained" color="primary" startIcon="⛹️">
|
||||
Hello World
|
||||
</Button>,
|
||||
)
|
||||
})
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import CheckboxLabels from './checkbox-labels'
|
||||
|
||||
it('renders checkboxes', () => {
|
||||
cy.viewport(600, 600)
|
||||
mount(<CheckboxLabels />)
|
||||
})
|
||||
@@ -1,116 +0,0 @@
|
||||
import React from 'react'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { green } from '@material-ui/core/colors'
|
||||
import FormGroup from '@material-ui/core/FormGroup'
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
||||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'
|
||||
import CheckBoxIcon from '@material-ui/icons/CheckBox'
|
||||
import Favorite from '@material-ui/icons/Favorite'
|
||||
import FavoriteBorder from '@material-ui/icons/FavoriteBorder'
|
||||
|
||||
const GreenCheckbox = withStyles({
|
||||
root: {
|
||||
color: green[400],
|
||||
'&$checked': {
|
||||
color: green[600],
|
||||
},
|
||||
},
|
||||
checked: {},
|
||||
})((props) => <Checkbox color="default" {...props} />)
|
||||
|
||||
export default function CheckboxLabels () {
|
||||
const [state, setState] = React.useState({
|
||||
checkedA: true,
|
||||
checkedB: true,
|
||||
checkedF: true,
|
||||
checkedG: true,
|
||||
})
|
||||
|
||||
const handleChange = (name) => {
|
||||
return (event) => {
|
||||
setState({ ...state, [name]: event.target.checked })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup row>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={state.checkedA}
|
||||
onChange={handleChange('checkedA')}
|
||||
value="checkedA"
|
||||
/>
|
||||
}
|
||||
label="Secondary"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={state.checkedB}
|
||||
onChange={handleChange('checkedB')}
|
||||
value="checkedB"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Primary"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={<Checkbox value="checkedC" />}
|
||||
label="Uncontrolled"
|
||||
/>
|
||||
<FormControlLabel
|
||||
disabled
|
||||
control={<Checkbox value="checkedD" />}
|
||||
label="Disabled"
|
||||
/>
|
||||
<FormControlLabel
|
||||
disabled
|
||||
control={<Checkbox checked value="checkedE" />}
|
||||
label="Disabled"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={state.checkedF}
|
||||
onChange={handleChange('checkedF')}
|
||||
value="checkedF"
|
||||
indeterminate
|
||||
/>
|
||||
}
|
||||
label="Indeterminate"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<GreenCheckbox
|
||||
checked={state.checkedG}
|
||||
onChange={handleChange('checkedG')}
|
||||
value="checkedG"
|
||||
/>
|
||||
}
|
||||
label="Custom color"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={<FavoriteBorder />}
|
||||
checkedIcon={<Favorite />}
|
||||
value="checkedH"
|
||||
/>
|
||||
}
|
||||
label="Custom icon"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
|
||||
checkedIcon={<CheckBoxIcon fontSize="small" />}
|
||||
value="checkedI"
|
||||
/>
|
||||
}
|
||||
label="Custom size"
|
||||
/>
|
||||
</FormGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import React from 'react'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import List from '@material-ui/core/List'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
import InboxIcon from '@material-ui/icons/Inbox'
|
||||
import DraftsIcon from '@material-ui/icons/Drafts'
|
||||
|
||||
const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
root: {
|
||||
width: '100%',
|
||||
maxWidth: 360,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
function ListItemLink (props) {
|
||||
return <ListItem button component="a" {...props} />
|
||||
}
|
||||
|
||||
export default function SimpleList () {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<List component="nav" aria-label="main mailbox folders">
|
||||
<ListItem button>
|
||||
<ListItemIcon>
|
||||
<InboxIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Inbox" />
|
||||
</ListItem>
|
||||
<ListItem button>
|
||||
<ListItemIcon>
|
||||
<DraftsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Drafts" />
|
||||
</ListItem>
|
||||
</List>
|
||||
<Divider />
|
||||
<List component="nav" aria-label="secondary mailbox folders">
|
||||
<ListItem button>
|
||||
<ListItemText primary="Trash" />
|
||||
</ListItem>
|
||||
<ListItemLink href="#simple-list">
|
||||
<ListItemText primary="Spam" />
|
||||
</ListItemLink>
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import { ListItemText } from '@material-ui/core'
|
||||
import SimpleList from './list-demo'
|
||||
|
||||
it('renders a list item', () => {
|
||||
mount(
|
||||
<ListItem>
|
||||
<ListItemText primary={'my example list item'} />
|
||||
</ListItem>,
|
||||
)
|
||||
|
||||
cy.contains('my example list item')
|
||||
})
|
||||
|
||||
// demo from https://material-ui.com/components/lists/
|
||||
it('renders full list', () => {
|
||||
cy.viewport(500, 800)
|
||||
mount(<SimpleList />)
|
||||
cy.contains('Drafts')
|
||||
.click()
|
||||
.wait(1000)
|
||||
.click()
|
||||
.wait(1000)
|
||||
.click()
|
||||
})
|
||||
@@ -1,269 +0,0 @@
|
||||
import { mount } from '@cypress/react'
|
||||
// select example from
|
||||
// https://material-ui.com/components/selects/
|
||||
import React from 'react'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import InputLabel from '@material-ui/core/InputLabel'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
import FormHelperText from '@material-ui/core/FormHelperText'
|
||||
import FormControl from '@material-ui/core/FormControl'
|
||||
import Select from '@material-ui/core/Select'
|
||||
|
||||
const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
formControl: {
|
||||
margin: theme.spacing(1),
|
||||
minWidth: 120,
|
||||
},
|
||||
selectEmpty: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
export default function SimpleSelect () {
|
||||
const classes = useStyles()
|
||||
const [age, setAge] = React.useState('')
|
||||
|
||||
const inputLabel = React.useRef(null)
|
||||
const [labelWidth, setLabelWidth] = React.useState(0)
|
||||
|
||||
React.useEffect(() => {
|
||||
setLabelWidth(inputLabel.current.offsetWidth)
|
||||
}, [])
|
||||
|
||||
const handleChange = (event) => {
|
||||
setAge(event.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel id="demo-simple-select-label">Age</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel id="demo-simple-select-helper-label">Age</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-helper-label"
|
||||
id="demo-simple-select-helper"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText>Some important helper text</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText>Without label</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel shrink id="demo-simple-select-placeholder-label-label">
|
||||
Age
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-placeholder-label-label"
|
||||
id="demo-simple-select-placeholder-label"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText>Label + placeholder</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl} disabled>
|
||||
<InputLabel id="demo-simple-select-disabled-label">Name</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-disabled-label"
|
||||
id="demo-simple-select-disabled"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText>Disabled</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl} error>
|
||||
<InputLabel id="demo-simple-select-error-label">Name</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-error-label"
|
||||
id="demo-simple-select-error"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
renderValue={(value) => `⚠️ - ${value}`}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText>Error</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel id="demo-simple-select-readonly-label">Name</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-readonly-label"
|
||||
id="demo-simple-select-readonly"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
inputProps={{ readOnly: true }}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText>Read only</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel id="demo-simple-select-autowidth-label">Age</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-autowidth-label"
|
||||
id="demo-simple-select-autowidth"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
autoWidth
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText>Auto width</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
<MenuItem value="" disabled>
|
||||
Placeholder
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText>Placeholder</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl required className={classes.formControl}>
|
||||
<InputLabel id="demo-simple-select-required-label">Age</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-required-label"
|
||||
id="demo-simple-select-required"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText>Required</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl variant="outlined" className={classes.formControl}>
|
||||
<InputLabel ref={inputLabel} id="demo-simple-select-outlined-label">
|
||||
Age
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-outlined-label"
|
||||
id="demo-simple-select-outlined"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
labelWidth={labelWidth}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl variant="filled" className={classes.formControl}>
|
||||
<InputLabel id="demo-simple-select-filled-label">Age</InputLabel>
|
||||
<Select
|
||||
labelId="demo-simple-select-filled-label"
|
||||
id="demo-simple-select-filled"
|
||||
value={age}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>Ten</MenuItem>
|
||||
<MenuItem value={20}>Twenty</MenuItem>
|
||||
<MenuItem value={30}>Thirty</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
it('renders selects', () => {
|
||||
cy.viewport(1200, 600)
|
||||
|
||||
mount(<SimpleSelect />, {
|
||||
stylesheets: [
|
||||
'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap',
|
||||
'https://fonts.googleapis.com/icon?family=Material+Icons',
|
||||
],
|
||||
})
|
||||
|
||||
cy.get('#demo-simple-select').click()
|
||||
cy.contains('[role=option]', 'Twenty')
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
// check that other select has changed
|
||||
cy.contains('#demo-simple-select-outlined', 'Twenty').should('be.visible')
|
||||
})
|
||||
@@ -1,62 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import { mount } from '@cypress/react'
|
||||
// select example from
|
||||
// https://material-ui.com/components/rating/
|
||||
|
||||
import React from 'react'
|
||||
import Rating from '@material-ui/lab/Rating'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Box from '@material-ui/core/Box'
|
||||
|
||||
export default function SimpleRating ({ onSetRating }) {
|
||||
const [value, setValue] = React.useState(2)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Box component="fieldset" mb={3} borderColor="transparent">
|
||||
<Typography component="legend">Controlled</Typography>
|
||||
<Rating
|
||||
name="simple-controlled"
|
||||
value={value}
|
||||
onChange={(event, newValue) => {
|
||||
setValue(newValue)
|
||||
console.log('new value', newValue)
|
||||
if (onSetRating) {
|
||||
onSetRating(newValue)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="fieldset" mb={3} borderColor="transparent">
|
||||
<Typography component="legend">Read only</Typography>
|
||||
<Rating name="read-only" value={value} readOnly />
|
||||
</Box>
|
||||
<Box component="fieldset" mb={3} borderColor="transparent">
|
||||
<Typography component="legend">Disabled</Typography>
|
||||
<Rating name="disabled" value={value} disabled />
|
||||
</Box>
|
||||
<Box component="fieldset" mb={3} borderColor="transparent">
|
||||
<Typography component="legend">Pristine</Typography>
|
||||
<Rating name="pristine" value={null} />
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
it('renders simple rating', () => {
|
||||
cy.viewport(300, 400)
|
||||
const onSetRating = cy.stub()
|
||||
|
||||
mount(<SimpleRating onSetRating={onSetRating} />, {
|
||||
stylesheets: [
|
||||
'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap',
|
||||
'https://fonts.googleapis.com/icon?family=Material+Icons',
|
||||
],
|
||||
})
|
||||
|
||||
cy.get('label[for=simple-controlled-4]')
|
||||
.click()
|
||||
.then(() => {
|
||||
expect(onSetRating).to.have.been.calledWith(4)
|
||||
})
|
||||
})
|
||||
@@ -1,107 +0,0 @@
|
||||
// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
|
||||
export const top100Films = [
|
||||
{ title: 'The Shawshank Redemption', year: 1994 },
|
||||
{ title: 'The Godfather', year: 1972 },
|
||||
{ title: 'The Godfather: Part II', year: 1974 },
|
||||
{ title: 'The Dark Knight', year: 2008 },
|
||||
{ title: '12 Angry Men', year: 1957 },
|
||||
{ title: 'Schindler\'s List', year: 1993 },
|
||||
{ title: 'Pulp Fiction', year: 1994 },
|
||||
{ title: 'The Lord of the Rings: The Return of the King', year: 2003 },
|
||||
{ title: 'The Good, the Bad and the Ugly', year: 1966 },
|
||||
{ title: 'Fight Club', year: 1999 },
|
||||
{ title: 'The Lord of the Rings: The Fellowship of the Ring', year: 2001 },
|
||||
{ title: 'Star Wars: Episode V - The Empire Strikes Back', year: 1980 },
|
||||
{ title: 'Forrest Gump', year: 1994 },
|
||||
{ title: 'Inception', year: 2010 },
|
||||
{ title: 'The Lord of the Rings: The Two Towers', year: 2002 },
|
||||
{ title: 'One Flew Over the Cuckoo\'s Nest', year: 1975 },
|
||||
{ title: 'Goodfellas', year: 1990 },
|
||||
{ title: 'The Matrix', year: 1999 },
|
||||
{ title: 'Seven Samurai', year: 1954 },
|
||||
{ title: 'Star Wars: Episode IV - A New Hope', year: 1977 },
|
||||
{ title: 'City of God', year: 2002 },
|
||||
{ title: 'Se7en', year: 1995 },
|
||||
{ title: 'The Silence of the Lambs', year: 1991 },
|
||||
{ title: 'It\'s a Wonderful Life', year: 1946 },
|
||||
{ title: 'Life Is Beautiful', year: 1997 },
|
||||
{ title: 'The Usual Suspects', year: 1995 },
|
||||
{ title: 'Léon: The Professional', year: 1994 },
|
||||
{ title: 'Spirited Away', year: 2001 },
|
||||
{ title: 'Saving Private Ryan', year: 1998 },
|
||||
{ title: 'Once Upon a Time in the West', year: 1968 },
|
||||
{ title: 'American History X', year: 1998 },
|
||||
{ title: 'Interstellar', year: 2014 },
|
||||
{ title: 'Casablanca', year: 1942 },
|
||||
{ title: 'City Lights', year: 1931 },
|
||||
{ title: 'Psycho', year: 1960 },
|
||||
{ title: 'The Green Mile', year: 1999 },
|
||||
{ title: 'The Intouchables', year: 2011 },
|
||||
{ title: 'Modern Times', year: 1936 },
|
||||
{ title: 'Raiders of the Lost Ark', year: 1981 },
|
||||
{ title: 'Rear Window', year: 1954 },
|
||||
{ title: 'The Pianist', year: 2002 },
|
||||
{ title: 'The Departed', year: 2006 },
|
||||
{ title: 'Terminator 2: Judgment Day', year: 1991 },
|
||||
{ title: 'Back to the Future', year: 1985 },
|
||||
{ title: 'Whiplash', year: 2014 },
|
||||
{ title: 'Gladiator', year: 2000 },
|
||||
{ title: 'Memento', year: 2000 },
|
||||
{ title: 'The Prestige', year: 2006 },
|
||||
{ title: 'The Lion King', year: 1994 },
|
||||
{ title: 'Apocalypse Now', year: 1979 },
|
||||
{ title: 'Alien', year: 1979 },
|
||||
{ title: 'Sunset Boulevard', year: 1950 },
|
||||
{
|
||||
title:
|
||||
'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
|
||||
year: 1964,
|
||||
},
|
||||
{ title: 'The Great Dictator', year: 1940 },
|
||||
{ title: 'Cinema Paradiso', year: 1988 },
|
||||
{ title: 'The Lives of Others', year: 2006 },
|
||||
{ title: 'Grave of the Fireflies', year: 1988 },
|
||||
{ title: 'Paths of Glory', year: 1957 },
|
||||
{ title: 'Django Unchained', year: 2012 },
|
||||
{ title: 'The Shining', year: 1980 },
|
||||
{ title: 'WALL·E', year: 2008 },
|
||||
{ title: 'American Beauty', year: 1999 },
|
||||
{ title: 'The Dark Knight Rises', year: 2012 },
|
||||
{ title: 'Princess Mononoke', year: 1997 },
|
||||
{ title: 'Aliens', year: 1986 },
|
||||
{ title: 'Oldboy', year: 2003 },
|
||||
{ title: 'Once Upon a Time in America', year: 1984 },
|
||||
{ title: 'Witness for the Prosecution', year: 1957 },
|
||||
{ title: 'Das Boot', year: 1981 },
|
||||
{ title: 'Citizen Kane', year: 1941 },
|
||||
{ title: 'North by Northwest', year: 1959 },
|
||||
{ title: 'Vertigo', year: 1958 },
|
||||
{ title: 'Star Wars: Episode VI - Return of the Jedi', year: 1983 },
|
||||
{ title: 'Reservoir Dogs', year: 1992 },
|
||||
{ title: 'Braveheart', year: 1995 },
|
||||
{ title: 'M', year: 1931 },
|
||||
{ title: 'Requiem for a Dream', year: 2000 },
|
||||
{ title: 'Amélie', year: 2001 },
|
||||
{ title: 'A Clockwork Orange', year: 1971 },
|
||||
{ title: 'Like Stars on Earth', year: 2007 },
|
||||
{ title: 'Taxi Driver', year: 1976 },
|
||||
{ title: 'Lawrence of Arabia', year: 1962 },
|
||||
{ title: 'Double Indemnity', year: 1944 },
|
||||
{ title: 'Eternal Sunshine of the Spotless Mind', year: 2004 },
|
||||
{ title: 'Amadeus', year: 1984 },
|
||||
{ title: 'To Kill a Mockingbird', year: 1962 },
|
||||
{ title: 'Toy Story 3', year: 2010 },
|
||||
{ title: 'Logan', year: 2017 },
|
||||
{ title: 'Full Metal Jacket', year: 1987 },
|
||||
{ title: 'Dangal', year: 2016 },
|
||||
{ title: 'The Sting', year: 1973 },
|
||||
{ title: '2001: A Space Odyssey', year: 1968 },
|
||||
{ title: 'Singin\' in the Rain', year: 1952 },
|
||||
{ title: 'Toy Story', year: 1995 },
|
||||
{ title: 'Bicycle Thieves', year: 1948 },
|
||||
{ title: 'The Kid', year: 1921 },
|
||||
{ title: 'Inglourious Basterds', year: 2009 },
|
||||
{ title: 'Snatch', year: 2000 },
|
||||
{ title: '3 Idiots', year: 2009 },
|
||||
{ title: 'Monty Python and the Holy Grail', year: 1975 },
|
||||
]
|
||||
@@ -1,7 +0,0 @@
|
||||
# MobX v6 example
|
||||
|
||||
Based on the example from [the docs](https://mobx.js.org/react-integration.html)
|
||||
|
||||
See [Timer.js](Timer.js) that has an observable class, [timer-view.jsx](timer-view.jsx) for React component linked to a timer instance, and [timer-spec.jsx](timer-spec.jsx) showing a component test.
|
||||
|
||||

|
||||
@@ -1,13 +0,0 @@
|
||||
import { makeAutoObservable } from 'mobx'
|
||||
|
||||
export class Timer {
|
||||
secondsPassed = 0
|
||||
|
||||
constructor () {
|
||||
makeAutoObservable(this)
|
||||
}
|
||||
|
||||
increaseTimer () {
|
||||
this.secondsPassed += 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 295 KiB |
@@ -1,8 +0,0 @@
|
||||
import React from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
// A function component wrapped with `observer` will react
|
||||
// to any future change in an observable it used before.
|
||||
export const TimerView = observer(({ timer }) => (
|
||||
<span>Seconds passed: {timer.secondsPassed}</span>
|
||||
))
|
||||
@@ -1,39 +0,0 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { Timer } from './Timer'
|
||||
import { TimerView } from './timer-view'
|
||||
|
||||
describe('MobX v6', () => {
|
||||
context('TimerView', () => {
|
||||
it('increments every second', () => {
|
||||
const myTimer = new Timer()
|
||||
|
||||
mount(<TimerView timer={myTimer} />)
|
||||
cy.contains('Seconds passed: 0').then(() => {
|
||||
// we can increment the timer from outside
|
||||
myTimer.increaseTimer()
|
||||
})
|
||||
|
||||
cy.contains('Seconds passed: 1')
|
||||
|
||||
// by wrapping the timer and giving it an alias
|
||||
cy.wrap(myTimer).as('timer')
|
||||
// we can "insert" it into the command chain
|
||||
// using cy.get() and then invoke methods
|
||||
// as if every command was inside .then(() => {...}) callback
|
||||
cy.get('@timer').invoke('increaseTimer')
|
||||
cy.contains('Seconds passed: 2')
|
||||
|
||||
cy.get('@timer').invoke('increaseTimer')
|
||||
cy.contains('Seconds passed: 3')
|
||||
cy.get('@timer').invoke('increaseTimer')
|
||||
cy.contains('Seconds passed: 4')
|
||||
|
||||
// we can also ask the timer for the current value
|
||||
cy.get('@timer').invoke('increaseTimer')
|
||||
cy.get('@timer')
|
||||
.its('secondsPassed')
|
||||
.should('equal', 5)
|
||||
})
|
||||
})
|
||||
})
|
||||
+4
-4
@@ -2,8 +2,7 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { Users } from './1-users.jsx'
|
||||
// to mock CommonJS module loaded from `node_modules` use "require" in spec file
|
||||
const Axios = require('axios')
|
||||
import axios from 'axios'
|
||||
|
||||
describe('Mocking Axios', () => {
|
||||
it('shows real users', () => {
|
||||
@@ -12,8 +11,9 @@ describe('Mocking Axios', () => {
|
||||
})
|
||||
|
||||
// https://github.com/bahmutov/@cypress/react/issues/338
|
||||
it('mocks axios.get', () => {
|
||||
cy.stub(Axios, 'get')
|
||||
// TODO: Support stubbing ES Modules. The above hack won't work with Vite.
|
||||
it.skip('mocks axios.get', () => {
|
||||
cy.stub(axios, 'get')
|
||||
.resolves({
|
||||
data: [
|
||||
{
|
||||
+4
-5
@@ -2,8 +2,7 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { Users } from './2-users-named.jsx'
|
||||
// to mock CommonJS module loaded from `node_modules` use "require" in spec file
|
||||
const Axios = require('axios')
|
||||
import axios from 'axios'
|
||||
|
||||
describe('Mocking Axios named import get', () => {
|
||||
it('shows real users', () => {
|
||||
@@ -11,9 +10,9 @@ describe('Mocking Axios named import get', () => {
|
||||
cy.get('li').should('have.length', 3)
|
||||
})
|
||||
|
||||
it('mocks get', () => {
|
||||
console.log('Axios', Axios)
|
||||
cy.stub(Axios, 'get')
|
||||
// TODO: Support stubbing ES Modules
|
||||
it.skip('mocks get', () => {
|
||||
cy.stub(axios, 'get')
|
||||
.resolves({
|
||||
data: [
|
||||
{
|
||||
+4
-4
@@ -2,7 +2,7 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { Users } from './3-users-api.jsx'
|
||||
import * as Axios from './axios-api'
|
||||
import * as axios from './axios-api'
|
||||
|
||||
describe('Mocking wrapped Axios', () => {
|
||||
it('shows real users', () => {
|
||||
@@ -10,9 +10,9 @@ describe('Mocking wrapped Axios', () => {
|
||||
cy.get('li').should('have.length', 3)
|
||||
})
|
||||
|
||||
it('mocks get', () => {
|
||||
console.log('Axios', Axios)
|
||||
cy.stub(Axios, 'get')
|
||||
// TODO: Support stubbing ES Modules
|
||||
it.skip('mocks get', () => {
|
||||
cy.stub(axios, 'get')
|
||||
.resolves({
|
||||
data: [
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
// import wrapped Axios method
|
||||
import { get } from './axios-api'
|
||||
import axiosApi from './axios-api'
|
||||
|
||||
export class Users extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -11,7 +11,8 @@ export class Users extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
get('https://jsonplaceholder.cypress.io/users?_limit=3').then((response) => {
|
||||
console.log({ axiosApi })
|
||||
axiosApi.get('https://jsonplaceholder.cypress.io/users?_limit=3').then((response) => {
|
||||
// JSON responses are automatically parsed.
|
||||
this.setState({
|
||||
users: response.data,
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// wrap Axios exports
|
||||
export * from 'axios'
|
||||
@@ -0,0 +1,4 @@
|
||||
// wrap Axios exports
|
||||
import axios from 'axios'
|
||||
|
||||
export default axios
|
||||
@@ -1,47 +0,0 @@
|
||||
# Mocking ES6 imports
|
||||
|
||||
The original example comes from https://reactjs.org/docs/testing-recipes.html#mocking-modules
|
||||
|
||||
The [contact.js](contact.js) component imports [map.js](map.js) component. But the real Map is expensive to render - it uses Google Maps, etc. Thus during tests we would like to replace the real Map with `DummyMap` component that only renders the props.
|
||||
|
||||
See [spec.js](spec.js) test file. The recommended approach is to mock the ES6 import.
|
||||
|
||||
```js
|
||||
// contact.js
|
||||
import Map from './map'
|
||||
export default function Contact(props) {
|
||||
// renders <Map ...>
|
||||
}
|
||||
|
||||
// spec.js
|
||||
import Contact from './contact'
|
||||
import * as MapModule from './map'
|
||||
|
||||
const DummyMap = props => (
|
||||
<div data-testid="map">
|
||||
DummyMap {props.center.lat}:{props.center.long}
|
||||
</div>
|
||||
)
|
||||
|
||||
it('renders stubbed Map', () => {
|
||||
// DummyMap component will be called with props and any other arguments
|
||||
cy.stub(MapModule, 'default').callsFake(DummyMap)
|
||||
|
||||
cy.viewport(500, 500)
|
||||
const center = { lat: 0, long: 0 }
|
||||
mount(
|
||||
<Contact
|
||||
name="Joni Baez"
|
||||
email="test@example.com"
|
||||
site="http://test.com"
|
||||
center={center}
|
||||
/>,
|
||||
)
|
||||
|
||||
cy.contains('Contact Joni Baez via')
|
||||
// confirm DummyMap renders "0:0" passed via props
|
||||
cy.contains('[data-testid="map"]', '0:0').should('be.visible')
|
||||
})
|
||||
```
|
||||
|
||||

|
||||
@@ -1,22 +0,0 @@
|
||||
// example from https://reactjs.org/docs/testing-recipes.html#mocking-modules
|
||||
import React from 'react'
|
||||
import Map from './map'
|
||||
|
||||
export default function Contact (props) {
|
||||
return (
|
||||
<div>
|
||||
<address>
|
||||
Contact {props.name} via{' '}
|
||||
<a data-testid="email" href={`mailto:${props.email}`}>
|
||||
email
|
||||
</a>{' '}
|
||||
or on their{' '}
|
||||
<a data-testid="site" href={props.site}>
|
||||
website
|
||||
</a>
|
||||
.
|
||||
</address>
|
||||
<Map center={props.center} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 176 KiB |
@@ -1,18 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import { GoogleMap, withGoogleMap, withScriptjs } from 'react-google-maps'
|
||||
|
||||
const GMap = withScriptjs(
|
||||
withGoogleMap((props) => <GoogleMap id="example-map" center={props.center} />),
|
||||
)
|
||||
|
||||
export default function Map (props) {
|
||||
return (
|
||||
<GMap
|
||||
googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyC4R6AN7SmujjPUIGKdyao2Kqitzr1kiRg&v=3.exp&libraries=geometry,drawing,places"
|
||||
loadingElement={<div style={{ height: `100%` }} />}
|
||||
containerElement={<div style={{ height: `400px` }} />}
|
||||
mapElement={<div style={{ height: `100%` }} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
// Component "Contact" has child component "Map" that is expensive to render
|
||||
import Contact from './contact'
|
||||
import * as MapModule from './map'
|
||||
|
||||
describe('Mock imported component', () => {
|
||||
// mock Map component used by Contact component
|
||||
// whenever React tries to instantiate using Map constructor
|
||||
// call DummyMap constructor
|
||||
const DummyMap = (props) => {
|
||||
return (
|
||||
<div data-testid="map">
|
||||
DummyMap {props.center.lat}:{props.center.long}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
context('via mocking ES6 default import', () => {
|
||||
// recommended
|
||||
it('renders stubbed Map', () => {
|
||||
// DummyMap component will be called with props and any other arguments
|
||||
cy.stub(MapModule, 'default').callsFake(DummyMap)
|
||||
|
||||
cy.viewport(500, 500)
|
||||
const center = { lat: 0, long: 0 }
|
||||
|
||||
mount(
|
||||
<Contact
|
||||
name="Joni Baez"
|
||||
email="test@example.com"
|
||||
site="http://test.com"
|
||||
center={center}
|
||||
/>,
|
||||
)
|
||||
|
||||
cy.contains('Contact Joni Baez via')
|
||||
// confirm DummyMap renders "0:0" passed via props
|
||||
cy.contains('[data-testid="map"]', '0:0').should('be.visible')
|
||||
})
|
||||
})
|
||||
})
|
||||
+2
-1
@@ -6,7 +6,8 @@ import * as services from './services'
|
||||
|
||||
const ingredients = ['bacon', 'tomato', 'mozzarella', 'pineapples']
|
||||
|
||||
describe('RemotePizza', () => {
|
||||
// TODO: Support stubbing ES Modules
|
||||
describe.skip('RemotePizza', () => {
|
||||
it('mocks named import from services', () => {
|
||||
cy.stub(services, 'fetchIngredients')
|
||||
.resolves({ args: { ingredients } })
|
||||
+2
-1
@@ -10,7 +10,8 @@ describe('Mocking ES6 import', () => {
|
||||
cy.contains('h1', 'real greeting').should('be.visible')
|
||||
})
|
||||
|
||||
it('shows mock greeting', () => {
|
||||
// TODO: Stub support for ES Modules in Vite.
|
||||
it.skip('shows mock greeting', () => {
|
||||
// stubbing ES6 named imports works via
|
||||
// @babel/plugin-transform-modules-commonjs with "loose: true"
|
||||
// because the generated properties are configurable
|
||||
@@ -1,9 +0,0 @@
|
||||
# radioactive-state example
|
||||
|
||||
Testing components that use [radioactive-state](https://github.com/MananTank/radioactive-state) library.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Tests [counter](./counter), [counters](./counters), [todos](./todos)
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react'
|
||||
import useRS from 'radioactive-state'
|
||||
|
||||
// click on the counter to increment
|
||||
|
||||
export const Counter = () => {
|
||||
// create a radioactive state
|
||||
const state = useRS({
|
||||
count: 0,
|
||||
})
|
||||
|
||||
// mutating the state triggers a re-render
|
||||
const increment = () => state.count++
|
||||
|
||||
return (
|
||||
<div className="count" onClick={increment}>
|
||||
{state.count}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
.App {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 5rem;
|
||||
color: white;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 100ms ease;
|
||||
background: #3069f9;
|
||||
border: 10px solid rgb(198, 214, 253);
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.count:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import './counter.css'
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { Counter } from './Counter.jsx'
|
||||
|
||||
describe('reactive-state Counter', () => {
|
||||
it('increments count on click', () => {
|
||||
mount(
|
||||
<div className="App">
|
||||
<Counter />
|
||||
</div>,
|
||||
)
|
||||
|
||||
cy.contains('.count', '0')
|
||||
.click()
|
||||
.click()
|
||||
.click()
|
||||
|
||||
cy.contains('.count', '3')
|
||||
})
|
||||
})
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from 'react'
|
||||
import useRS from 'radioactive-state'
|
||||
|
||||
// deep mutation also triggers re-render !
|
||||
const Counters = () => {
|
||||
const state = useRS({
|
||||
counts: [0],
|
||||
sum: 0,
|
||||
})
|
||||
|
||||
const increment = (i) => {
|
||||
state.counts[i]++
|
||||
state.sum++
|
||||
}
|
||||
|
||||
const addCounter = () => state.counts.push(0)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={addCounter}> Add Counter </button>
|
||||
<div className="counts">
|
||||
{state.counts.map((count, i) => (
|
||||
<div className="count" onClick={() => increment(i)} key={i}>
|
||||
{count}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="count sum">{state.sum}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Counters
|
||||
@@ -1,77 +0,0 @@
|
||||
.App {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.count {
|
||||
background: #3069f9;
|
||||
border: 6px solid rgb(198, 214, 253);
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
|
||||
margin: 10px;
|
||||
font-size: 2rem;
|
||||
color: white;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 100ms ease;
|
||||
}
|
||||
|
||||
.count.sum {
|
||||
background: #ff3944;
|
||||
border-color: #ffb0b5;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.counts {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.count:not(.sum):active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #ffd400;
|
||||
border: 5px solid #fdee81;
|
||||
outline: none;
|
||||
font-size: 1.5rem;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin: 20px;
|
||||
transition: all 150ms ease;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import './counters.css'
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import Counters from './Counters.jsx'
|
||||
|
||||
describe('reactive-state Counters', () => {
|
||||
it('increments single count on click', () => {
|
||||
mount(
|
||||
<div className="App">
|
||||
<Counters />
|
||||
</div>,
|
||||
)
|
||||
|
||||
cy.contains('.count', '0')
|
||||
.click()
|
||||
.click()
|
||||
.click()
|
||||
|
||||
// increments the counter itself
|
||||
cy.contains('.count', '3')
|
||||
// increments the sum
|
||||
cy.contains('.sum', '3')
|
||||
|
||||
// add two more counters
|
||||
cy.contains('Add Counter')
|
||||
.click()
|
||||
.click()
|
||||
|
||||
cy.get('.counts .count').should('have.length', 3)
|
||||
// clicking the new counters increments the sum
|
||||
cy.get('.count')
|
||||
.eq(1)
|
||||
.click()
|
||||
|
||||
cy.contains('.sum', '4')
|
||||
cy.get('.count')
|
||||
.eq(2)
|
||||
.click()
|
||||
|
||||
cy.contains('.sum', '5')
|
||||
})
|
||||
})
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 369 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 457 KiB |
@@ -1,29 +0,0 @@
|
||||
import React from 'react'
|
||||
import useRS from 'radioactive-state'
|
||||
|
||||
const AddTodo = ({ onAdd }) => {
|
||||
const state = useRS({
|
||||
input: '',
|
||||
})
|
||||
|
||||
// handle events
|
||||
const handleEnter = (e) => e.key === 'Enter' && handleAdd()
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!state.input) return // ignore empty input
|
||||
|
||||
const todo = { title: state.input, completed: false } // make todo
|
||||
|
||||
onAdd(todo) // add it
|
||||
state.input = '' // clear input
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="add-todo">
|
||||
<input {...state.$input} onKeyDown={handleEnter} />
|
||||
<button onClick={handleAdd}> Add </button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddTodo
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
// todo is a reactive prop,
|
||||
// mutating it triggers re-render in parent component: Todos
|
||||
const Todo = ({ todo, onRemove }) => {
|
||||
const className = todo.completed ? 'todo completed' : 'todo'
|
||||
|
||||
const toggleTodo = () => {
|
||||
todo.completed = !todo.completed // 🤩
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="todo_title" onClick={toggleTodo}>
|
||||
{todo.title}
|
||||
</div>
|
||||
<div className="todo_remove" onClick={onRemove}>
|
||||
x
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Todo
|
||||
@@ -1,31 +0,0 @@
|
||||
import React from 'react'
|
||||
import useRS from 'radioactive-state'
|
||||
import Todo from './Todo'
|
||||
import AddTodo from './AddTodo'
|
||||
|
||||
const Todos = () => {
|
||||
const state = useRS({
|
||||
todos: [],
|
||||
})
|
||||
|
||||
const removeTodo = (i) => state.todos.splice(i, 1)
|
||||
const addTodo = (todo) => state.todos.push(todo)
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<AddTodo onAdd={addTodo} />
|
||||
|
||||
{!state.todos.length && <div className="no-todos"> No Todos added </div>}
|
||||
|
||||
<div className="todos">
|
||||
{state.todos.map((todo, i) => {
|
||||
return (
|
||||
<Todo todo={todo} key={i} onRemove={() => removeTodo(i)} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Todos
|
||||
@@ -1,98 +0,0 @@
|
||||
.App {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
font-size: 1.5rem;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 50px;
|
||||
}
|
||||
|
||||
.add-todo {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
border: 4px solid #313d52;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.todos {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.todo {
|
||||
padding-right: 1em;
|
||||
cursor: pointer;
|
||||
background: #eef1f6;
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 1em;
|
||||
font-size: 1.2rem;
|
||||
box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #313d52;
|
||||
}
|
||||
|
||||
.todo_title {
|
||||
flex-grow: 1;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.todo_remove {
|
||||
background: #dde3ed;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #6d7e9b;
|
||||
}
|
||||
|
||||
.completed {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
.completed .todo_title {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
display: block;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
input {
|
||||
flex-grow: 1;
|
||||
padding: 1rem;
|
||||
line-height: 1;
|
||||
display: block;
|
||||
color: #313d52;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 1rem;
|
||||
flex-shrink: 0;
|
||||
background: #313d52;
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.no-todos {
|
||||
color: #a0aec0;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import './todos.css'
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import Todos from './Todos'
|
||||
|
||||
describe('reactive-state Todos', () => {
|
||||
it('adds and removes todos', () => {
|
||||
mount(
|
||||
<div className="App">
|
||||
<Todos />
|
||||
</div>,
|
||||
)
|
||||
|
||||
cy.get('.add-todo input').type('code{enter}')
|
||||
cy.get('.add-todo input').type('test')
|
||||
cy.get('.add-todo')
|
||||
.contains('Add')
|
||||
.click()
|
||||
|
||||
// now check things
|
||||
cy.get('.todos .todo').should('have.length', 2)
|
||||
// remove the first one
|
||||
cy.get('.todos .todo')
|
||||
.first()
|
||||
.should('contain', 'code')
|
||||
.find('.todo_remove')
|
||||
.click()
|
||||
|
||||
// single todo left
|
||||
cy.get('.todos .todo')
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain', 'test')
|
||||
})
|
||||
})
|
||||
@@ -1,15 +0,0 @@
|
||||
App and tests modeled from https://github.com/softchris/react-book/tree/7bd767bb39f59977b107d07f383a8f4e32a12857/Testing/test-demo for https://softchris.github.io/books/react/
|
||||
|
||||
## Selecting React Components
|
||||
|
||||
Typically we suggest selecting DOM elements using public properties likes data attributes, labels, text, CSS class names, or ids. If you really want to select React components using props or state values, combine `@cypress/react` with [cypress-react-selector](https://github.com/abhinaba-ghosh/cypress-react-selector) plugin.
|
||||
|
||||
See file [./src/components/ProductsList.spec.js](./src/components/ProductsList.spec.js) for example.
|
||||
|
||||
```js
|
||||
// find a single instance with prop
|
||||
// <AProduct name={'Second item'} />
|
||||
cy.react('AProduct', { name: 'Second item' })
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Second item')
|
||||
```
|
||||
@@ -1,32 +0,0 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #222;
|
||||
height: 150px;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-title {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.App-intro {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import React, { Component } from 'react'
|
||||
import logo from './logo.svg'
|
||||
import './App.css'
|
||||
import Todos from './Todos'
|
||||
import Select from './Select'
|
||||
|
||||
class App extends Component {
|
||||
render () {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<h1 className="App-title">Welcome to React</h1>
|
||||
</header>
|
||||
<div className="App-intro">
|
||||
<Todos todos={[{ title: 'test' }]} />
|
||||
<Select />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -1,25 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import { mount } from '@cypress/react'
|
||||
import React from 'react'
|
||||
import Select from './Note'
|
||||
|
||||
describe('Note', () => {
|
||||
it('save text', () => {
|
||||
mount(<Select />)
|
||||
cy.get('#change').type('input text')
|
||||
cy.contains('button', 'Save').click()
|
||||
cy.get('[data-testid=saved]').should('have.text', 'Saved: input text')
|
||||
})
|
||||
|
||||
it('load data', () => {
|
||||
mount(<Select />)
|
||||
cy.contains('button', 'Load').click()
|
||||
// there is a built-in delay in loading the data
|
||||
// but we don't worry about it - we just check if the text eventually appears
|
||||
cy.get('[data-testid=item]')
|
||||
.should('have.length', 2)
|
||||
.and('be.visible')
|
||||
.first()
|
||||
.should('have.text', 'test')
|
||||
})
|
||||
})
|
||||
@@ -1,59 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
class Note extends React.Component {
|
||||
state = {
|
||||
content: '',
|
||||
saved: '',
|
||||
}
|
||||
|
||||
onChange = (evt) => {
|
||||
this.setState({
|
||||
content: evt.target.value,
|
||||
})
|
||||
|
||||
console.log('updating content')
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.setState({
|
||||
saved: `Saved: ${this.state.content}`,
|
||||
})
|
||||
}
|
||||
|
||||
load = () => {
|
||||
let me = this
|
||||
|
||||
setTimeout(() => {
|
||||
me.setState({
|
||||
data: [{ title: 'test' }, { title: 'test2' }],
|
||||
})
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<label htmlFor="change">Change text</label>
|
||||
<input id="change" placeholder="change text" onChange={this.onChange} />
|
||||
<div data-testid="saved">{this.state.saved}</div>
|
||||
{this.state.data && (
|
||||
<div data-testid="data">
|
||||
{this.state.data.map((item) => {
|
||||
return (
|
||||
<div data-testid="item" className="item">
|
||||
{item.title}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<button onClick={this.save}>Save</button>
|
||||
<button onClick={this.load}>Load</button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Note
|
||||
@@ -1,3 +0,0 @@
|
||||
.selected {
|
||||
background: green;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user