diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index 58463853349a..b359a6485157 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -30,8 +30,9 @@ mainBuildFilters: &mainBuildFilters - /^release\/\d+\.\d+\.\d+$/ # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - 'update-v8-snapshot-cache-on-develop' - - 'mschile/chrome_for_testing' + - 'chore/update_vue_test_utils' - 'publish-binary' + - 'cacie/29590/document-domain-subdomains' # usually we don't build Mac app - it takes a long time # but sometimes we want to really confirm we are doing the right thing @@ -42,7 +43,7 @@ macWorkflowFilters: &darwin-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'mschile/chrome_for_testing', << pipeline.git.branch >> ] + - equal: [ 'chore/update_vue_test_utils', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -53,7 +54,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'mschile/chrome_for_testing', << pipeline.git.branch >> ] + - equal: [ 'chore/update_vue_test_utils', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -76,7 +77,7 @@ windowsWorkflowFilters: &windows-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'mschile/chrome_for_testing', << pipeline.git.branch >> ] + - equal: [ 'ryanm/chore/electron-33-upgrade', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -85,7 +86,7 @@ executors: # the Docker image with Cypress dependencies and Chrome browser cy-doc: docker: - - image: cypress/browsers-internal:node18.17.1-chrome128-ff131 + - image: cypress/browsers-internal:node20.18.1-bullseye-chrome131-ff133 # by default, we use "medium" to balance performance + CI costs. bump or reduce on a per-job basis if needed. resource_class: medium environment: @@ -94,7 +95,7 @@ executors: kitchensink-executor: docker: - - image: cypress/browsers-internal:node20.15.0-chrome126-ff131 + - image: cypress/browsers-internal:node20.18.1-bullseye-chrome131-ff133 # by default, we use "medium" to balance performance + CI costs. bump or reduce on a per-job basis if needed. resource_class: medium environment: @@ -104,7 +105,7 @@ executors: # Docker image with non-root "node" user non-root-docker-user: docker: - - image: cypress/browsers-internal:node18.17.1-chrome128-ff131 + - image: cypress/browsers-internal:node20.18.1-bullseye-chrome131-ff133 user: node environment: PLATFORM: linux @@ -136,7 +137,7 @@ executors: linux-arm64: &linux-arm64-executor machine: - image: ubuntu-2004:2023.07.1 + image: ubuntu-2004:2024.05.1 resource_class: arm.medium environment: PLATFORM: linux @@ -152,7 +153,7 @@ commands: name: Set environment variable to determine whether or not to persist artifacts command: | echo "Setting SHOULD_PERSIST_ARTIFACTS variable" - echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "mschile/chrome_for_testing" ]]; then + echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "chore/update_vue_test_utils" ]]; then export SHOULD_PERSIST_ARTIFACTS=true fi' >> "$BASH_ENV" # You must run `setup_should_persist_artifacts` command and be using bash before running this command @@ -361,7 +362,7 @@ commands: steps: - restore_cache: name: Restore cache state, to check for known modules cache existence - key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}-centos7 + key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}-better-sqlite3 - unless: condition: <> steps: @@ -383,6 +384,24 @@ commands: - run: name: Install Node Modules command: | + if [[ `node ./scripts/get-platform-key.js` == 'linux-arm64' ]]; then + # Building better-sqlite3 on arm64 requires gcc-10 + # on Arm, CI runs as non-root so we need to use sudo + sudo apt install gcc-10 g++-10 + + # Update default to gcc-10 and g++-10 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 30 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 30 + sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc 30 + sudo update-alternatives --set cc /usr/bin/gcc + sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++ 30 + sudo update-alternatives --set c++ /usr/bin/g++ + sudo update-alternatives --config gcc + sudo update-alternatives --config g++ + + # If we are on arm64 we need to install setuptools as it no longer comes standard with the latest version of python + pip install setuptools + fi source ./scripts/ensure-node.sh # avoid installing Percy's Chromium every time we use @percy/cli # https://docs.percy.io/docs/caching-asset-discovery-browser-in-ci @@ -420,7 +439,7 @@ commands: steps: - save_cache: name: Saving node-modules cache state key - key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}-centos7 + key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}-better-sqlite3 paths: - node_modules_installed - unless: @@ -513,33 +532,32 @@ commands: echo "Location of Google Chrome Installation: `which google-chrome-<>`" echo "Google Chrome Version: `google-chrome-<> --version`" - # This code builds better-sqlite3 on CentOS 7. This is necessary because CentOS 7 has the oldest glibc version - # that we support. This job uses the cypress/centos7-builder https://hub.docker.com/repository/docker/cypress/centos7-builder/general - # image to build better-sqlite3 against an older version of glibc (2.17). - # Since this is running Docker remote, we need to copy the project into the container, and copy the built plugin out - # of the container because the host running docker does not have access to the project directory so volume mounts are - # not possible. The built plugin is copied to the project directory so it can be injected into the final binary. + # This code builds better-sqlite3 on Debian 10 (Buster). This is necessary because Debian 10 has the oldest glibc version (2.28) that we support. + # + # Since this is running Docker remote (because the job running the command may not be using an executor with the appropriate glibc version), we need to + # copy the project into the container, and copy the built plugin out of the container because the host running docker does not have access to the + # project directory so volume mounts are not possible. The built plugin is copied to the project directory so it can be injected into the final binary. build-better-sqlite3: - description: Build better-sqlite3 for CentOS 7 + description: Build better-sqlite3 for glibc 2.28 steps: - setup_remote_docker - run: - name: Build better-sqlite3 for CentOS 7 + name: Build better-sqlite3 for glibc 2.28 command: | if [[ ! -f better_sqlite3.node ]]; then set -x apt update && apt install -y docker.io - docker run -d --name centos7-builder cypress/centos7-builder:latest /bin/bash -c "sleep 1000000000" - docker cp ~/cypress/node_modules/better-sqlite3 centos7-builder:/better-sqlite3 - docker exec -it centos7-builder /bin/bash -c "cd /better-sqlite3 && source /root/.bashrc && chown -R root:root . && npm install --ignore-scripts && npx --no-install prebuild -r electron -t 27.1.3 --include-regex 'better_sqlite3.node$'" - docker cp centos7-builder:/better-sqlite3/build/Release/better_sqlite3.node ~/cypress/node_modules/better-sqlite3/build/Release/better_sqlite3.node - docker rm -f centos7-builder + docker run -d --name better-sqlite3-builder cypress/base-internal:20.15.0-buster-python3.8-gcc-10.5 /bin/bash -c "sleep 1000000000" + docker cp ~/cypress/node_modules/better-sqlite3 better-sqlite3-builder:/better-sqlite3 + docker exec -it better-sqlite3-builder /bin/bash -c "cd /better-sqlite3 && source /root/.bashrc && chown -R root:root . && npm install --ignore-scripts && npx --no-install prebuild -r electron -t 33.2.1 --include-regex 'better_sqlite3.node$'" + docker cp better-sqlite3-builder:/better-sqlite3/build/Release/better_sqlite3.node ~/cypress/node_modules/better-sqlite3/build/Release/better_sqlite3.node + docker rm -f better-sqlite3-builder cp ~/cypress/node_modules/better-sqlite3/build/Release/better_sqlite3.node ~/cypress/better_sqlite3.node else cp ~/cypress/better_sqlite3.node ~/cypress/node_modules/better-sqlite3/build/Release/better_sqlite3.node fi - save_cache: - key: better-sqlite3-{{ checksum "node_modules/better-sqlite3/package.json" }}-{{ checksum "node_modules/electron/package.json" }}-centos7 + key: better-sqlite3-{{ checksum "node_modules/better-sqlite3/package.json" }}-{{ checksum "node_modules/electron/package.json" }} paths: - better_sqlite3.node - run: @@ -556,6 +574,11 @@ commands: description: chrome channel to install type: string default: '' + inject-document-domain: + description: run subset of tests with injectDocumentDomain config enabled + type: boolean + default: false + steps: - restore_cached_workspace - when: @@ -577,11 +600,20 @@ commands: echo Current working directory is $PWD echo Total containers $CIRCLE_NODE_TOTAL + + if << parameters.inject-document-domain >> ; then + YARN_CMD="cypress:run:inject-document-domain" + PARALLEL="" + else + YARN_CMD="cypress:run" + PARALLEL="--parallel --group 5x-driver-<>" + fi + if [[ -v MAIN_RECORD_KEY ]]; then # internal PR CYPRESS_RECORD_KEY=$MAIN_RECORD_KEY \ CYPRESS_INTERNAL_ENABLE_TELEMETRY="true" \ - yarn cypress:run --record --parallel --group 5x-driver-<> --browser <> --runner-ui + yarn $YARN_CMD --record $PARALLEL --browser <> --runner-ui else # external PR TESTFILES=$(circleci tests glob "cypress/e2e/**/*.cy.*" | circleci tests split --total=$CIRCLE_NODE_TOTAL) @@ -590,7 +622,7 @@ commands: if [[ -z "$TESTFILES" ]]; then echo "Empty list of test files" fi - yarn cypress:run --browser <> --spec $TESTFILES --runner-ui + yarn $YARN_CMD --browser <> --spec $TESTFILES --runner-ui fi working_directory: packages/driver - verify-mocha-results @@ -1280,7 +1312,7 @@ commands: command: ls -la types working_directory: cli/build - run: - command: ls -la vue vue2 mount-utils react + command: ls -la vue mount-utils react working_directory: cli/build - unless: condition: @@ -1772,7 +1804,7 @@ jobs: PLATFORM: linux machine: # using `machine` gives us a Linux VM that can run Docker - image: ubuntu-2004:2023.07.1 + image: ubuntu-2004:2024.05.1 docker_layer_caching: true resource_class: medium steps: @@ -1783,7 +1815,7 @@ jobs: working_directory: ~/cypress docker: # we need an image with yarn 4 berry installed on it to run this test - - image: cypress/base-internal:18.17.1-yarn-berry + - image: cypress/base-internal:20.18.1-yarn-berry environment: # needed to inform the bootstrap-docker-container.sh script to link the binary in the system-test project directory REPO_DIR: /root/cypress @@ -1811,6 +1843,32 @@ jobs: command: | # we need to bootstrap the binary into our project directory and run the tests source ./system-tests/scripts/bootstrap-docker-container.sh 'yarn cypress run' + # We can remove this job a once https://github.com/sveltejs/svelte-loader/issues/243 is resolved. + svelte-webpack-system-test: + parallelism: 1 + working_directory: ~/cypress + docker: + - image: cypress/browsers-internal:node20.18.1-bullseye-chrome131-ff133 + environment: + # needed to inform the bootstrap-docker-container.sh script to link the binary in the system-test project directory + REPO_DIR: /root/cypress + TEST_PROJECT_DIR: ./system-tests/projects/svelte-webpack-configured + USE_YARN_TO_INSTALL_CYPRESS_BINARY: true + steps: + - maybe_skip_binary_jobs + - attach_workspace: + at: ~/ + - run: + name: Install monorepo dependencies + command: yarn install --ignore-scripts + - run: + name: install dependencies + command: cd ./system-tests/projects/svelte-webpack-configured && yarn + - run: + name: Bootstrap the Cypress binary and run binary system test + command: | + # we need to bootstrap the binary into our project directory and run the tests + source ./system-tests/scripts/bootstrap-docker-container.sh 'yarn cypress run --component --spec src/mount.cy.ts src/App.cy.ts' system-tests-chrome: <<: *defaults @@ -1959,6 +2017,16 @@ jobs: - run-driver-integration-tests: browser: chrome install-chrome-channel: stable + + driver-integration-tests-chrome-inject-document-domain: + <<: *defaults + parallelism: 5 + resource_class: medium+ + steps: + - run-driver-integration-tests: + browser: chrome + install-chrome-channel: stable + inject-document-domain: true driver-integration-tests-chrome-beta: <<: *defaults @@ -1969,6 +2037,16 @@ jobs: browser: chrome:beta install-chrome-channel: beta + driver-integration-tests-chrome-beta-inject-document-domain: + <<: *defaults + resource_class: medium+ + parallelism: 5 + steps: + - run-driver-integration-tests: + browser: chrome:beta + install-chrome-channel: beta + inject-document-domain: true + driver-integration-tests-firefox: <<: *defaults resource_class: medium+ @@ -1991,6 +2069,8 @@ jobs: steps: - run-driver-integration-tests: browser: webkit + # inject document domain must be true for webkit, as cy.origin is not supported + inject-document-domain: true run-reporter-component-tests-chrome: <<: *defaults @@ -2131,6 +2211,10 @@ jobs: - run: name: Build command: yarn lerna run build --scope=@cypress/vue + - run: + name: Run tests + command: yarn test + working_directory: npm/vue - store_test_results: path: npm/vue/test_results - store_artifacts: @@ -2146,15 +2230,6 @@ jobs: command: yarn lerna run build --scope=@cypress/angular - store-npm-logs - npm-angular-signals: - <<: *defaults - steps: - - restore_cached_workspace - - run: - name: Build - command: yarn lerna run build --scope=@cypress/angular-signals - - store-npm-logs - npm-puppeteer-unit-tests: <<: *defaults steps: @@ -2778,7 +2853,8 @@ linux-x64-workflow: &linux-x64-workflow - run-vite-dev-server-integration-tests - driver-integration-tests-firefox - driver-integration-tests-chrome - - driver-integration-tests-chrome-beta + - driver-integration-tests-chrome-inject-document-domain + - driver-integration-tests-chrome-beta-inject-document-domain - driver-integration-tests-electron - driver-integration-tests-webkit - driver-integration-memory-tests @@ -2835,10 +2911,18 @@ linux-x64-workflow: &linux-x64-workflow context: test-runner:cypress-record-key requires: - build + - driver-integration-tests-chrome-inject-document-domain: + context: test-runner:cypress-record-key + requires: + - build - driver-integration-tests-chrome-beta: context: test-runner:cypress-record-key requires: - build + - driver-integration-tests-chrome-beta-inject-document-domain: + context: test-runner:cypress-record-key + requires: + - build - driver-integration-tests-firefox: context: test-runner:cypress-record-key requires: @@ -2926,9 +3010,6 @@ linux-x64-workflow: &linux-x64-workflow - npm-angular: requires: - build - - npm-angular-signals: - requires: - - build - npm-mount-utils: requires: - build @@ -2947,7 +3028,6 @@ linux-x64-workflow: &linux-x64-workflow requires: - check-ts - npm-angular - - npm-angular-signals - npm-eslint-plugin-dev - npm-puppeteer-unit-tests - npm-puppeteer-cypress-tests @@ -2966,6 +3046,8 @@ linux-x64-workflow: &linux-x64-workflow - driver-integration-tests-firefox - driver-integration-tests-chrome - driver-integration-tests-chrome-beta + - driver-integration-tests-chrome-inject-document-domain + - driver-integration-tests-chrome-beta-inject-document-domain - driver-integration-tests-electron - driver-integration-tests-webkit - driver-integration-memory-tests @@ -2985,6 +3067,7 @@ linux-x64-workflow: &linux-x64-workflow - test-npm-module-on-minimum-node-version - binary-system-tests - yarn-pnp-preprocessor-system-test + - svelte-webpack-system-test - test-kitchensink - unit-tests - verify-release-readiness @@ -3104,6 +3187,11 @@ linux-x64-workflow: &linux-x64-workflow requires: - get-published-artifacts - system-tests-node-modules-install + - svelte-webpack-system-test: + context: publish-binary + requires: + - get-published-artifacts + - system-tests-node-modules-install linux-x64-contributor-workflow: &linux-x64-contributor-workflow jobs: @@ -3194,10 +3282,18 @@ linux-x64-contributor-workflow: &linux-x64-contributor-workflow context: test-runner:cypress-record-key requires: - contributor-pr + - driver-integration-tests-chrome-inject-document-domain: + context: test-runner:cypress-record-key + requires: + - contributor-pr - driver-integration-tests-chrome-beta: context: test-runner:cypress-record-key requires: - contributor-pr + - driver-integration-tests-chrome-beta-inject-document-domain: + context: test-runner:cypress-record-key + requires: + - contributor-pr - driver-integration-tests-firefox: context: test-runner:cypress-record-key requires: @@ -3285,9 +3381,6 @@ linux-x64-contributor-workflow: &linux-x64-contributor-workflow - npm-angular: requires: - build - - npm-angular-signals: - requires: - - build - npm-mount-utils: requires: - build @@ -3305,7 +3398,6 @@ linux-x64-contributor-workflow: &linux-x64-contributor-workflow requires: - check-ts - npm-angular - - npm-angular-signals - npm-eslint-plugin-dev - npm-puppeteer-unit-tests - npm-puppeteer-cypress-tests diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.yml b/.github/ISSUE_TEMPLATE/1-bug-report.yml index 2f269318a1d8..19d6046515e1 100644 --- a/.github/ISSUE_TEMPLATE/1-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/1-bug-report.yml @@ -43,7 +43,7 @@ body: attributes: label: Node version description: What version of node.js are you using to run Cypress? - placeholder: ex. v18.17.0 + placeholder: ex. v20.18.1 validations: required: true - type: input diff --git a/.github/ISSUE_TEMPLATE/2-memory-issue.yml b/.github/ISSUE_TEMPLATE/2-memory-issue.yml index 28e5bbabb1a2..f492fbb8d201 100644 --- a/.github/ISSUE_TEMPLATE/2-memory-issue.yml +++ b/.github/ISSUE_TEMPLATE/2-memory-issue.yml @@ -51,7 +51,7 @@ body: attributes: label: Node version description: What version of node.js are you using to run Cypress? - placeholder: ex. v18.17.0 + placeholder: ex. v20.18.1 validations: required: true - type: input diff --git a/.github/ISSUE_TEMPLATE/3-install-issue.yml b/.github/ISSUE_TEMPLATE/3-install-issue.yml index 6e513901e5fc..2a4ce852ce3d 100644 --- a/.github/ISSUE_TEMPLATE/3-install-issue.yml +++ b/.github/ISSUE_TEMPLATE/3-install-issue.yml @@ -38,7 +38,7 @@ body: attributes: label: Node version description: What version of node.js are you using to run Cypress? - placeholder: ex. v18.17.0 + placeholder: ex. v20.18.1 validations: required: true - type: dropdown diff --git a/.github/workflows/snyk_sca_scan.yaml b/.github/workflows/snyk_sca_scan.yaml index 45f504bf3a4f..608be5ec7a83 100644 --- a/.github/workflows/snyk_sca_scan.yaml +++ b/.github/workflows/snyk_sca_scan.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x] + node-version: [20.x] steps: - name: Checkout uses: actions/checkout@v4 @@ -28,7 +28,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: 'yarn' - name: Run yarn run: yarn diff --git a/.github/workflows/snyk_static_analysis_scan.yaml b/.github/workflows/snyk_static_analysis_scan.yaml index 58596a407c8f..b0e0087f1ddb 100644 --- a/.github/workflows/snyk_static_analysis_scan.yaml +++ b/.github/workflows/snyk_static_analysis_scan.yaml @@ -21,7 +21,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: 'yarn' - name: Run yarn run: yarn diff --git a/.github/workflows/update-browser-versions.yml b/.github/workflows/update-browser-versions.yml index 330464273fbb..935ffc247c4a 100644 --- a/.github/workflows/update-browser-versions.yml +++ b/.github/workflows/update-browser-versions.yml @@ -30,7 +30,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Check for new Chrome versions id: get-versions uses: actions/github-script@v7 diff --git a/.github/workflows/update_v8_snapshot_cache.yml b/.github/workflows/update_v8_snapshot_cache.yml index 0c2454ec9554..4cc2c1462009 100644 --- a/.github/workflows/update_v8_snapshot_cache.yml +++ b/.github/workflows/update_v8_snapshot_cache.yml @@ -88,7 +88,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: 'yarn' - name: Run yarn # set the timeout here to try and deal with Windows slowness diff --git a/.gitignore b/.gitignore index 2047278fbf2f..1109337211a9 100644 --- a/.gitignore +++ b/.gitignore @@ -295,15 +295,11 @@ typings/ # next.js build output .next -# nuxt.js build output -.nuxt - # rollup.js default build output dist/ # Uncomment the public line if your project uses Gatsby # https://nextjs.org/blog/next-9-1#public-directory-support -# https://create-react-app.dev/docs/using-the-public-folder/#docsNav # public # Storybook build outputs diff --git a/.node-version b/.node-version index 4a1f488b6c3b..d4b7699d36ca 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.17.1 +20.18.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index c387bf62a53c..ccf4888e233c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,12 @@ - [Cypress App](https://on.cypress.io/changelog) - [`@cypress/angular`](https://github.com/cypress-io/cypress/blob/develop/npm/angular/CHANGELOG.md) -- [`@cypress/angular-signals`](https://github.com/cypress-io/cypress/blob/develop/npm/angular-signals/CHANGELOG.md) - [`@cypress/eslint-plugin-dev`](https://github.com/cypress-io/cypress/blob/develop/npm/eslint-plugin-dev/CHANGELOG.md) - [`@cypress/mount-utils`](https://github.com/cypress-io/cypress/blob/develop/npm/mount-utils/CHANGELOG.md) - [`@cypress/react`](https://github.com/cypress-io/cypress/blob/develop/npm/react/CHANGELOG.md) -- [`@cypress/react18`](https://github.com/cypress-io/cypress/blob/develop/npm/react18/CHANGELOG.md) - [`@cypress/svelte`](https://github.com/cypress-io/cypress/blob/develop/npm/svelte/CHANGELOG.md) - [`@cypress/vite-dev-server`](https://github.com/cypress-io/cypress/blob/develop/npm/vite-dev-server/CHANGELOG.md) - [`@cypress/vue`](https://github.com/cypress-io/cypress/blob/develop/npm/vue/CHANGELOG.md) -- [`@cypress/vue2`](https://github.com/cypress-io/cypress/blob/develop/npm/vue2/CHANGELOG.md) - [`@cypress/webpack-batteries-included-preprocessor`](https://github.com/cypress-io/cypress/blob/develop/npm/webpack-batteries-included-preprocessor/CHANGELOG.md) - [`@cypress/webpack-dev-server`](https://github.com/cypress-io/cypress/blob/develop/npm/webpack-dev-server/CHANGELOG.md) - [`@cypress/webpack-preprocessor`](https://github.com/cypress-io/cypress/blob/develop/npm/webpack-preprocessor/CHANGELOG.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b8db568f7c2..3ee8c7d81040 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -182,17 +182,14 @@ Here is a list of the npm packages in this repository: | Folder Name | Package Name | Purpose | | :----------------------------------------------------- | :--------------------------------- | :--------------------------------------------------------------------------- | | [angular](./npm/angular) | `@cypress/angular` | Cypress component testing for Angular. | - | [angular signals](./npm/angular-signals) | `@cypress/angular-signals` | Cypress component testing for Angular 17/18 including support for signals. | | [eslint-plugin-dev](./npm/eslint-plugin-dev) | `@cypress/eslint-plugin-dev` | Eslint plugin for internal development. | | [grep](./npm/grep) | `@cypress/grep` | Filter tests using substring | | [mount-utils](./npm/mount-utils) | `@cypress/mount-utils` | Common functionality for Vue/React/Angular adapters. | | [react](./npm/react) | `@cypress/react` | Cypress component testing for React. | - | [react18](./npm/react18) | `@cypress/react18` | Cypress component testing for React 18. | | [schematic](./npm/cypress-schematic) | `@cypress/schematic` | Official Angular Schematic and Builder for the Angular CLI.| | [svelte](./npm/svelte) | `@cypress/svelte` | Cypress component testing for Svelte. | | [vite-dev-server](./npm/vite-dev-server) | `@cypress/vite-dev-server` | Vite powered dev server for Component Testing. | | [vue](./npm/vue) | `@cypress/vue` | Cypress component testing for Vue 3. | - | [vue2](./npm/vue2) | `@cypress/vue2` | Cypress component testing for Vue 2. | | [webpack-batteries-included-preprocessor](./npm/webpack-batteries-included-preprocessor) | `@cypress/webpack-batteries-included-preprocessor` | Cypress preprocessor for bundling JavaScript via webpack with dependencies included and support for various ES features, TypeScript, and CoffeeScript. | | [webpack-dev-server](./npm/webpack-dev-server) | `@cypress/webpack-dev-server` | Webpack powered dev server for Component Testing. | | [webpack-preprocessor](./npm/webpack-preprocessor) | `@cypress/webpack-preprocessor` | Cypress preprocessor for bundling JavaScript via webpack. | @@ -205,36 +202,19 @@ You must have the following installed on your system to contribute locally: - [`Node.js`](https://nodejs.org/en/) (See the root [.node-version](.node-version) file for the required version. You can find a list of tools on [node-version-usage](https://github.com/shadowspawn/node-version-usage) to switch the version of [`Node.js`](https://nodejs.org/en/) based on [.node-version](.node-version).) - [`yarn`](https://yarnpkg.com/en/docs/install) -- [`python`](https://www.python.org/downloads/) (since we use `node-gyp`. See their [repo](https://github.com/nodejs/node-gyp) for Python version requirements. Use Python `3.11` or lower.) +- [`python`](https://www.python.org/downloads/) (since we use `node-gyp`. See their [repo](https://github.com/nodejs/node-gyp) for Python version requirements.) #### Debian/Ubuntu `sudo apt install g++ make` meets the additional requirements to run `node-gyp` in the context of building Cypress from source. `python` is pre-installed on Debian-based systems including Ubuntu. -The Python versions shipped with Ubuntu versions `20.04` and `22.04` are compatible with Cypress requirements. +The Python versions shipped with Ubuntu versions `20.04`, `22.04` and `24.*` are compatible with Cypress requirements. -Only on Ubuntu `24.04` install Python `3.11` by executing the following commands: - -```shell -sudo add-apt-repository ppa:deadsnakes/ppa -sudo apt update -sudo apt install python3.11 -``` - -Add the environment variable `NODE_GYP_FORCE_PYTHON` to `~/.bashrc`: - -```shell -export NODE_GYP_FORCE_PYTHON=/usr/bin/python3.11 -``` - -> [!IMPORTANT] -> The above steps to install an earlier version of Python are currently not available for the interim release Ubuntu `24.10`. The highest compatible Ubuntu version for rebuilding Cypress from source error-free is currently Ubuntu `24.04`. - -For Ubuntu `24.04` refer also to the [Release notes](https://discourse.ubuntu.com/t/noble-numbat-release-notes/39890) in the section [Unprivileged user namespace restrictions](https://discourse.ubuntu.com/t/noble-numbat-release-notes/39890#unprivileged-user-namespace-restrictions-15) and apply one of the workarounds to disable unprivileged user namespace restrictions for the entire system, either for one boot or persistently, as described. If you do not do this you may receive an error which includes the text `FATAL:setuid_sandbox_host.cc` when you try to run Cypress on this version of Ubuntu after building Cypress from source. +For Ubuntu `24.04` and above, refer also to the [Ubuntu 24.04 Release notes](https://discourse.ubuntu.com/t/noble-numbat-release-notes/39890) in the section "Unprivileged user namespace restrictions" and apply one of the workarounds to disable unprivileged user namespace restrictions for the entire system, either for one boot or persistently, as described. If you do not do this you may receive an error which includes the text `FATAL:setuid_sandbox_host.cc` when you try to run Cypress on these versions of Ubuntu after building Cypress from source. #### Windows -When installing the Visual Studio C++ environment recommended by [node-gyp](https://github.com/nodejs/node-gyp), install also a Windows 10 SDK. The currently used version of `node-gyp` may otherwise fail to recognise the Visual Studio installation. +Currently no additional instructions for installation requirements. ### Getting Started diff --git a/cli/.eslintignore b/cli/.eslintignore index a0fd3df705de..90471aa76fd3 100644 --- a/cli/.eslintignore +++ b/cli/.eslintignore @@ -10,10 +10,7 @@ package.json # these are all copied from dist'd builds from the individual libs /angular -/angular-signals /react -/react18 /vue -/vue2 /svelte /mount-utils \ No newline at end of file diff --git a/cli/.gitignore b/cli/.gitignore index 81806971abcd..1c68b02ce1e5 100644 --- a/cli/.gitignore +++ b/cli/.gitignore @@ -16,9 +16,7 @@ build # ignore packages synced at build-time via # the sync-exported-npm-with-cli.js script vue -vue2 react* mount-utils angular svelte -angular-signals diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 17722b2a32bb..7443b288e350 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,12 +1,75 @@ -## 13.17.1 +## 14.0.0 -_Released 12/31/2024 (PENDING)_ +_Released 1/7/2024 (PENDING)_ + +**Breaking Changes:** + +- Removed support for Node.js 16 and Node.js 21. Addresses [#29930](https://github.com/cypress-io/cypress/issues/29930). +- Upgraded bundled Node.js version from `18.17.0` to `20.18.1`. Addresses [#29547](https://github.com/cypress-io/cypress/issues/29547). +- Prebuilt binaries for Linux are no longer compatible with Linux distributions based on glibc <2.28, for example: Ubuntu 14-18, RHEL 7, CentOS 7, Amazon Linux 2. Addresses [#29601](https://github.com/cypress-io/cypress/issues/29601). +- Cypress now only officially supports the latest 3 major versions of Chrome, Firefox, and Edge - older browser versions may still work, but we recommend keeping your browsers up to date to ensure compatibility with Cypress. A warning will no longer be displayed on browser selection in the Launchpad for any 'unsupported' browser versions. Additionally, the undocumented `minSupportedVersion` property has been removed from `Cypress.browser`. Addressed in [#30462](https://github.com/cypress-io/cypress/pull/30462). +- The `cy.origin()` command must now be used when navigating between subdomains. Because this is a fairly disruptive change for users who frequently navigate between subdomains, a new configuration option is being introduced. `injectDocumentDomain` can be set to `true` in order to re-enable the injection of `document.domain` by Cypress. This configuration option is marked as deprecated and you will receive a warning when Cypress is launched with this option set to `true`. It will be removed in Cypress 15. Addressed in [#30770](https://github.com/cypress-io/cypress/pull/30770). Addresses [#25806](https://github.com/cypress-io/cypress/issues/25806), [#25987](https://github.com/cypress-io/cypress/issues/25987), [#27528](https://github.com/cypress-io/cypress/issues/27528), [#29445](https://github.com/cypress-io/cypress/issues/29445), [#29590](https://github.com/cypress-io/cypress/issues/29590) and [#30571](https://github.com/cypress-io/cypress/issues/30571). +- It is no longer possible to make a `fetch` or `XMLHttpRequest` request from the `about:blank` page in Electron (i.e. `cy.window().then((win) => win.fetch(''))`). You must use `cy.request` instead or perform some form of initial navigation via `cy.visit()`. Addressed in [#29547](https://github.com/cypress-io/cypress/pull/30394). +- The `experimentalJustInTimeCompile` configuration option for component testing has been replaced with a `justInTimeCompile` option that is `true` by default. This option will only compile resources directly related to your spec, compiling them 'just-in-time' before spec execution. This should result in improved memory management and performance for component tests in `cypress open` and `cypress run` modes, in particular for large component testing suites. `justInTimeCompile` is now only supported for [`webpack`](https://www.npmjs.com/package/webpack). Addresses [#30234](https://github.com/cypress-io/cypress/issues/30234). Addressed in [#30402](https://github.com/cypress-io/cypress/pull/30402). +- Cypress Component Testing no longer supports: + - `create-react-app`. Addresses [#30028](https://github.com/cypress-io/cypress/issues/30028). + - `@vue/cli-service`. Addresses [#30481](https://github.com/cypress-io/cypress/issues/30481). + - `Angular` versions 13, 14, 15, and 16. The minimum supported version is now `17.2.0` in order to fully support Angular [signals](https://angular.dev/guide/signals). Addresses [#29582](https://github.com/cypress-io/cypress/issues/29582). Addressed in [#30539](https://github.com/cypress-io/cypress/pull/30539). + - `Next.js` versions 10, 11, 12, and 13. Addresses [#29583](https://github.com/cypress-io/cypress/issues/29583). + - `Nuxt.js` version 2. Addresses [#30468](https://github.com/cypress-io/cypress/issues/30468). + - `React` versions 16 and 17. Addresses [#29607](https://github.com/cypress-io/cypress/issues/29607). + - `Svelte` versions 3 and 4. Addresses [#30492](https://github.com/cypress-io/cypress/issues/30492) and [#30692](https://github.com/cypress-io/cypress/issues/30692). + - `Vue` version 2. Addresses [#30295](https://github.com/cypress-io/cypress/issues/30295). +- The `cypress/react18` test harness is no longer included in the Cypress binary. Instead, React 18 support is now shipped with `cypress/react`! Addresses [#29607](https://github.com/cypress-io/cypress/issues/29607). +- The `cypress/angular-signals` test harness is no longer included in the Cypress binary. Instead, signals support is now shipped with `cypress/angular`! This requires `rxjs` to be installed as a `peerDependency`. Addresses [#29606](https://github.com/cypress-io/cypress/issues/29606). +- The Cypress configuration wizard for Component Testing supports TypeScript 4.0 or greater. Addresses [#30493](https://github.com/cypress-io/cypress/issues/30493). +- `@cypress/webpack-dev-server` no longer supports `webpack-dev-server` version 3. Additionally, `@cypress/webpack-dev-server` now ships with `webpack-dev-server` version 5 by default. `webpack-dev-server` version 4 will need to be installed along side Cypress if you are still using `webpack` version 4. Addresses [#29308](https://github.com/cypress-io/cypress/issues/29308), [#30347](https://github.com/cypress-io/cypress/issues/30347), and [#30141](https://github.com/cypress-io/cypress/issues/30141). +- `@cypress/vite-dev-server` no longer supports `vite` versions 2 and 3. Addresses [#29377](https://github.com/cypress-io/cypress/issues/29377) and [#29378](https://github.com/cypress-io/cypress/issues/29378). +- The `delayMs` option of `cy.intercept()` has been removed. This option was deprecated in Cypress 6.4.0. Please use the `delay` option instead. Addressed in [#30463](https://github.com/cypress-io/cypress/pull/30463). +- The `experimentalFetchPolyfill` configuration option was removed. This option was deprecated in Cypress 6.0.0. We recommend using `cy.intercept()` for handling fetch requests. Addressed in [#30466](https://github.com/cypress-io/cypress/pull/30466). +- We removed yielding the second argument of `before:browser:launch` as an array of browser arguments. This behavior has been deprecated since Cypress 4.0.0. Addressed in [#30460](https://github.com/cypress-io/cypress/pull/30460). +- The `cypress open-ct` and `cypress run-ct` CLI commands were removed. Please use `cypress open --component` or `cypress run --component` respectively instead. Addressed in [#30456](https://github.com/cypress-io/cypress/pull/30456) +- The undocumented methods `Cypress.backend('firefox:force:gc')` and `Cypress.backend('log:memory:pressure')` were removed. Addresses [#30222](https://github.com/cypress-io/cypress/issues/30222). + +**Deprecations:** + +- The `resourceType` option on `cy.intercept` has been deprecated. We anticipate the resource types to change or be completely removed in the future. Our intention is to replace essential functionality dependent on the `resourceType` within Cypress in a future version (like hiding network logs that are not fetch/xhr). Please leave feedback on any essential uses of `resourceType` +in this [GitHub issue](https://github.com/cypress-io/cypress/issues/30447). Addresses [#30433](https://github.com/cypress-io/cypress/issues/30433). +- The new `injectDocumentDomain` configuration option is released as deprecated. It will be removed in Cypress 15. Addressed in [#30770](https://github.com/cypress-io/cypress/pull/30770). + +**Features:** + +- `injectDocumentDomain`, a new configuration option, can be set to `true` in order to re-enable the injection of `document.domain` by Cypress. Addressed in [#30770](https://github.com/cypress-io/cypress/pull/30770). +- Cypress Component Testing now supports: + - `Next.js` version >=15.0.4. Versions 15.0.0 - 15.0.3 depend on the React 19 Release Candidate and are not officially supported by Cypress, but should still work. Addresses [#30445](https://github.com/cypress-io/cypress/issues/30445). + - `React` version 19. Addresses [#29470](https://github.com/cypress-io/cypress/issues/29470). + - `Angular` version 19. Addresses [#30175](https://github.com/cypress-io/cypress/issues/30175). + - `Vite` version 6. Addresses [#30591](https://github.com/cypress-io/cypress/issues/30591). + - `Svelte` version 5. Addresses [#29641](https://github.com/cypress-io/cypress/issues/29641). + +**Bugfixes:** + +- Elements with `display: contents` will no longer use box model calculations for visibility, and correctly show as visible when it is visible. Fixed in [#29680](https://github.com/cypress-io/cypress/pull/29680). Fixes [#29605](https://github.com/cypress-io/cypress/issues/29605). +- Fixed a visibility issue when the element is positioned `static` or `relative` and the element's offset parent is positioned `absolute`, a descendent of the ancestor, and has no clippable overflow. Fixed in [#29689](https://github.com/cypress-io/cypress/pull/29689). Fixes [#28638](https://github.com/cypress-io/cypress/issues/28638). +- Fixed a visibility issue for elements with `textContent` but without a width or height. Fixed in [#29688](https://github.com/cypress-io/cypress/pull/29688). Fixes [#29687](https://github.com/cypress-io/cypress/issues/29687). +- Elements whose parent elements has `overflow: clip` and no height/width will now correctly show as hidden. Fixed in [#29778](https://github.com/cypress-io/cypress/pull/29778). Fixes [#23852](https://github.com/cypress-io/cypress/issues/23852). +- The CSS pseudo-class `:dir()` is now supported when testing in Electron. Addresses [#29766](https://github.com/cypress-io/cypress/issues/29766). **Misc:** +- Removed some component testing API stubs that were removed in [Cypress v11.0.0](https://docs.cypress.io/app/references/migration-guide#Component-Testing-Updates). Addressed in [#30696](https://github.com/cypress-io/cypress/pull/30696). Addresses [#30623](https://github.com/cypress-io/cypress/issues/30623). - Updated to use Cypress design system browser icons. Addressed in [#30790](https://github.com/cypress-io/cypress/pull/30790). +**Dependency Updates:** + +- Upgraded `electron` from `27.3.10` to `33.2.1`. Addresses [#29547](https://github.com/cypress-io/cypress/issues/29547) and [#30561](https://github.com/cypress-io/cypress/issues/30561). +- Upgraded `@electron/rebuild` from `3.2.10` to `3.7.1`. Addresses [#28766](https://github.com/cypress-io/cypress/issues/28766) and [#30632](https://github.com/cypress-io/cypress/issues/30632). +- Upgraded bundled Chromium version from `118.0.5993.159` to `130.0.6723.137`. Addresses [#29547](https://github.com/cypress-io/cypress/issues/29547) and [#30561](https://github.com/cypress-io/cypress/issues/30561). +- Updated `jQuery` from `3.4.1` to `3.7.1`. Addressed in [#30345](https://github.com/cypress-io/cypress/pull/30345). +- Updated `react` from `17.0.2` to `18.3.1` and `react-dom` from `17.0.2` to `18.3.1`. Addresses [#30511](https://github.com/cypress-io/cypress/issues/30511). +- Upgraded [`@vue/test-utils`](https://www.npmjs.com/package/@vue/test-utils) from `2.3.2` to `2.4.6`. Addresses [#26628](https://github.com/cypress-io/cypress/issues/26628). + ## 13.17.0 _Released 12/17/2024_ diff --git a/cli/__snapshots__/cli_spec.js b/cli/__snapshots__/cli_spec.js index feed7158b7d4..300796300cf2 100644 --- a/cli/__snapshots__/cli_spec.js +++ b/cli/__snapshots__/cli_spec.js @@ -223,10 +223,6 @@ exports['cli help command shows help 1'] = ` version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI - open-ct [options] Opens Cypress component testing interactive mode. - Deprecated: use "open --component" - run-ct [options] Runs all Cypress component testing suites. Deprecated: - use "run --component" install [options] Installs the Cypress executable matching this package's version verify [options] Verifies that Cypress is installed correctly and @@ -263,10 +259,6 @@ exports['cli help command shows help for -h 1'] = ` version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI - open-ct [options] Opens Cypress component testing interactive mode. - Deprecated: use "open --component" - run-ct [options] Runs all Cypress component testing suites. Deprecated: - use "run --component" install [options] Installs the Cypress executable matching this package's version verify [options] Verifies that Cypress is installed correctly and @@ -303,10 +295,6 @@ exports['cli help command shows help for --help 1'] = ` version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI - open-ct [options] Opens Cypress component testing interactive mode. - Deprecated: use "open --component" - run-ct [options] Runs all Cypress component testing suites. Deprecated: - use "run --component" install [options] Installs the Cypress executable matching this package's version verify [options] Verifies that Cypress is installed correctly and @@ -344,10 +332,6 @@ exports['cli unknown command shows usage and exits 1'] = ` version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI - open-ct [options] Opens Cypress component testing interactive mode. - Deprecated: use "open --component" - run-ct [options] Runs all Cypress component testing suites. Deprecated: - use "run --component" install [options] Installs the Cypress executable matching this package's version verify [options] Verifies that Cypress is installed correctly and @@ -457,10 +441,6 @@ exports['cli CYPRESS_INTERNAL_ENV allows and warns when staging environment 1'] version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI - open-ct [options] Opens Cypress component testing interactive mode. - Deprecated: use "open --component" - run-ct [options] Runs all Cypress component testing suites. Deprecated: - use "run --component" install [options] Installs the Cypress executable matching this package's version verify [options] Verifies that Cypress is installed correctly and diff --git a/cli/lib/cli.js b/cli/lib/cli.js index 5cec42cafb1c..7ac2f855defc 100644 --- a/cli/lib/cli.js +++ b/cli/lib/cli.js @@ -137,8 +137,6 @@ const knownCommands = [ 'install', 'open', 'run', - 'open-ct', - 'run-ct', 'verify', '-v', '--version', @@ -482,80 +480,6 @@ module.exports = { .catch(util.logErrorExit1) }) - program - .command('open-ct') - .usage('[options]') - .description('Opens Cypress component testing interactive mode. Deprecated: use "open --component"') - .option('-b, --browser ', text('browser')) - .option('-c, --config ', text('config')) - .option('-C, --config-file ', text('configFile')) - .option('-d, --detached [bool]', text('detached'), coerceFalse) - .option('-e, --env ', text('env')) - .option('--global', text('global')) - .option('-p, --port ', text('port')) - .option('-P, --project ', text('project')) - .option('--dev', text('dev'), coerceFalse) - .action((opts) => { - debug('opening Cypress') - - const msg = ` - ${logSymbols.warning} Warning: open-ct is deprecated and will be removed in a future release. - - Use \`cypress open --component\` instead. - ` - - logger.warn() - logger.warn(stripIndent(msg)) - logger.warn() - - require('./exec/open') - .start({ ...util.parseOpts(opts), testingType: 'component' }) - .then(util.exit) - .catch(util.logErrorExit1) - }) - - program - .command('run-ct') - .usage('[options]') - .description('Runs all Cypress component testing suites. Deprecated: use "run --component"') - .option('-b, --browser ', text('browser')) - .option('--ci-build-id ', text('ciBuildId')) - .option('-c, --config ', text('config')) - .option('-C, --config-file ', text('configFile')) - .option('-e, --env ', text('env')) - .option('--group ', text('group')) - .option('-k, --key ', text('key')) - .option('--headed', text('headed')) - .option('--headless', text('headless')) - .option('--no-exit', text('exit')) - .option('--parallel', text('parallel')) - .option('-p, --port ', text('port')) - .option('-P, --project ', text('project')) - .option('-q, --quiet', text('quiet')) - .option('--record [bool]', text('record'), coerceFalse) - .option('-r, --reporter ', text('reporter')) - .option('-o, --reporter-options ', text('reporterOptions')) - .option('-s, --spec ', text('spec')) - .option('-t, --tag ', text('tag')) - .option('--dev', text('dev'), coerceFalse) - .action((opts) => { - debug('running Cypress run-ct') - - const msg = ` - ${logSymbols.warning} Warning: run-ct is deprecated and will be removed in a future release. - Use \`cypress run --component\` instead. - ` - - logger.warn() - logger.warn(stripIndent(msg)) - logger.warn() - - require('./exec/run') - .start({ ...util.parseOpts(opts), testingType: 'component' }) - .then(util.exit) - .catch(util.logErrorExit1) - }) - program .command('install') .usage('[options]') diff --git a/cli/lib/exec/spawn.js b/cli/lib/exec/spawn.js index da25f03b8224..2affe11bf6c4 100644 --- a/cli/lib/exec/spawn.js +++ b/cli/lib/exec/spawn.js @@ -39,12 +39,6 @@ const isDbusWarning = /Failed to connect to the bus:/ // ERROR: No matching issuer found const isCertVerifyProcBuiltin = /(^\[.*ERROR:cert_verify_proc_builtin\.cc|^----- Certificate i=0 \(OU=Cypress Proxy|^ERROR: No matching issuer found$)/ -// Electron logs a benign warning about WebSwapCGLLayer on MacOS v12 and Electron v18 due to a naming collision in shared libraries. -// Once this is fixed upstream this regex can be removed: https://github.com/electron/electron/issues/33685 -// Sample: -// objc[60540]: Class WebSwapCGLLayer is implemented in both /System/Library/Frameworks/WebKit.framework/Versions/A/Frameworks/WebCore.framework/Versions/A/Frameworks/libANGLE-shared.dylib (0x7ffa5a006318) and /{path/to/app}/node_modules/electron/dist/Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib (0x10f8a89c8). One of the two will be used. Which one is undefined. -const isMacOSElectronWebSwapCGLLayerWarning = /^objc\[\d+\]: Class WebSwapCGLLayer is implemented in both.*Which one is undefined\./ - /** * Electron logs benign warnings about Vulkan when run on hosts that do not have a GPU. This is coming from the primary Electron process, * and not the browser being used for tests. @@ -74,7 +68,23 @@ const isContainerVulkanDriverWarning = /^Warning: vkCreateInstance/ const isContainerVulkanStack = /^\s*at (CheckVkSuccessImpl|CreateVkInstance|Initialize|Create|operator).+(VulkanError|BackendVk).cpp/ -const GARBAGE_WARNINGS = [isXlibOrLibudevRe, isHighSierraWarningRe, isRenderWorkerRe, isDbusWarning, isCertVerifyProcBuiltin, isMacOSElectronWebSwapCGLLayerWarning, isHostVulkanDriverWarning, isContainerVulkanDriverWarning, isContainerVulkanStack] +/** + * In Electron 32.0.0 a new debug scenario log message started appearing when iframes navigate to about:blank. This is a benign message. + * https://github.com/electron/electron/issues/44368 + * Sample: + * [78887:1023/114920.074882:ERROR:debug_utils.cc(14)] Hit debug scenario: 4 + */ +const isDebugScenario4 = /^\[[^\]]+debug_utils\.cc[^\]]+\] Hit debug scenario: 4/ + +/** + * In Electron 32.0.0 a new EGL driver message started appearing when running on Linux. This is a benign message. + * https://github.com/electron/electron/issues/43415 + * Sample: + * [78887:1023/114920.074882:ERROR:gl_display.cc(14)] EGL Driver message (Error) eglQueryDeviceAttribEXT: Bad attribute. + */ +const isEGLDriverMessage = /^\[[^\]]+gl_display\.cc[^\]]+\] EGL Driver message \(Error\) eglQueryDeviceAttribEXT: Bad attribute\./ + +const GARBAGE_WARNINGS = [isXlibOrLibudevRe, isHighSierraWarningRe, isRenderWorkerRe, isDbusWarning, isCertVerifyProcBuiltin, isHostVulkanDriverWarning, isContainerVulkanDriverWarning, isContainerVulkanStack, isDebugScenario4, isEGLDriverMessage] const isGarbageLineWarning = (str) => { return _.some(GARBAGE_WARNINGS, (re) => { diff --git a/cli/package.json b/cli/package.json index 52ad05a13970..62377435c439 100644 --- a/cli/package.json +++ b/cli/package.json @@ -68,15 +68,12 @@ "@babel/cli": "7.24.8", "@babel/preset-env": "7.25.3", "@cypress/angular": "0.0.0-development", - "@cypress/angular-signals": "0.0.0-development", "@cypress/grep": "0.0.0-development", "@cypress/mount-utils": "0.0.0-development", "@cypress/react": "0.0.0-development", - "@cypress/react18": "0.0.0-development", "@cypress/sinon-chai": "2.9.1", "@cypress/svelte": "0.0.0-development", "@cypress/vue": "0.0.0-development", - "@cypress/vue2": "0.0.0-development", "@packages/root": "0.0.0-development", "@types/bluebird": "3.5.33", "@types/chai": "4.2.15", @@ -96,7 +93,7 @@ "execa-wrap": "1.4.0", "hasha": "5.2.2", "mocha": "6.2.2", - "mock-fs": "5.2.0", + "mock-fs": "5.4.0", "mocked-env": "1.3.2", "nock": "13.2.9", "proxyquire": "2.1.3", @@ -116,17 +113,14 @@ "mount-utils", "vue", "react", - "vue2", - "react18", "angular", - "svelte", - "angular-signals" + "svelte" ], "bin": { "cypress": "bin/cypress" }, "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "types": "types", "exports": { @@ -140,11 +134,6 @@ "import": "./vue/dist/cypress-vue.esm-bundler.js", "require": "./vue/dist/cypress-vue.cjs.js" }, - "./vue2": { - "types": "./vue2/dist/index.d.ts", - "import": "./vue2/dist/cypress-vue2.esm-bundler.js", - "require": "./vue2/dist/cypress-vue2.cjs.js" - }, "./package.json": { "import": "./package.json", "require": "./package.json" @@ -154,11 +143,6 @@ "import": "./react/dist/cypress-react.esm-bundler.js", "require": "./react/dist/cypress-react.cjs.js" }, - "./react18": { - "types": "./react18/dist/index.d.ts", - "import": "./react18/dist/cypress-react.esm-bundler.js", - "require": "./react18/dist/cypress-react.cjs.js" - }, "./mount-utils": { "types": "./mount-utils/dist/index.d.ts", "require": "./mount-utils/dist/index.js" @@ -172,11 +156,6 @@ "types": "./svelte/dist/index.d.ts", "import": "./svelte/dist/cypress-svelte.esm-bundler.js", "require": "./svelte/dist/cypress-svelte.cjs.js" - }, - "./angular-signals": { - "types": "./angular-signals/dist/index.d.ts", - "import": "./angular-signals/dist/index.js", - "require": "./angular-signals/dist/index.js" } }, "workspaces": { diff --git a/cli/scripts/post-build.js b/cli/scripts/post-build.js index 752578adf983..e31b9e2cf807 100644 --- a/cli/scripts/post-build.js +++ b/cli/scripts/post-build.js @@ -9,11 +9,8 @@ shell.set('-e') // any error is fatal const npmModulesToCopy = [ 'mount-utils', 'react', - 'react18', 'vue', - 'vue2', 'angular', - 'angular-signals', 'svelte', ] diff --git a/cli/test/lib/cli_spec.js b/cli/test/lib/cli_spec.js index 3e0de35ddfae..5d17558ffc41 100644 --- a/cli/test/lib/cli_spec.js +++ b/cli/test/lib/cli_spec.js @@ -635,34 +635,10 @@ describe('cli', () => { expect(spawn.start.firstCall.args[0]).to.include('component') }) - it('spawns server with correct args for deprecated component-testing command', () => { - this.exec('open-ct --dev') - expect(spawn.start.firstCall.args[0]).to.include('--testing-type') - expect(spawn.start.firstCall.args[0]).to.include('component') - }) - it('runs server with correct args for component-testing', () => { this.exec('run --component --dev') expect(spawn.start.firstCall.args[0]).to.include('--testing-type') expect(spawn.start.firstCall.args[0]).to.include('component') }) - - it('runs server with correct args for deprecated component-testing command', () => { - this.exec('run-ct --dev') - expect(spawn.start.firstCall.args[0]).to.include('--testing-type') - expect(spawn.start.firstCall.args[0]).to.include('component') - }) - - it('does display open-ct command in the help', () => { - return execa('bin/cypress', ['help']).then((result) => { - expect(result).to.include('open-ct') - }) - }) - - it('does display run-ct command in the help', () => { - return execa('bin/cypress', ['help']).then((result) => { - expect(result).to.include('run-ct') - }) - }) }) }) diff --git a/cli/test/lib/exec/spawn_spec.js b/cli/test/lib/exec/spawn_spec.js index 76a0f7799c66..f6756cfe2c08 100644 --- a/cli/test/lib/exec/spawn_spec.js +++ b/cli/test/lib/exec/spawn_spec.js @@ -83,8 +83,6 @@ describe('lib/exec/spawn', function () { ----- Certificate i=0 (OU=Cypress Proxy Server Certificate,O=Cypress Proxy CA,L=Internet,ST=Internet,C=Internet,CN=www.googletagmanager.com) ----- ERROR: No matching issuer found - objc[60540]: Class WebSwapCGLLayer is implemented in both /System/Library/Frameworks/WebKit.framework/Versions/A/Frameworks/WebCore.framework/Versions/A/Frameworks/libANGLE-shared.dylib (0x7ffa5a006318) and /{path/to/app}/node_modules/electron/dist/Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib (0x10f8a89c8). One of the two will be used. Which one is undefined. - Warning: loader_scanned_icd_add: Driver /usr/lib/x86_64-linux-gnu/libvulkan_intel.so supports Vulkan 1.2, but only supports loader interface version 4. Interface version 5 or newer required to support this version of Vulkan (Policy #LDP_DRIVER_7) Warning: loader_scanned_icd_add: Driver /usr/lib/x86_64-linux-gnu/libvulkan_lvp.so supports Vulkan 1.1, but only supports loader interface version 4. Interface version 5 or newer required to support this version of Vulkan (Policy #LDP_DRIVER_7) Warning: loader_scanned_icd_add: Driver /usr/lib/x86_64-linux-gnu/libvulkan_radeon.so supports Vulkan 1.2, but only supports loader interface version 4. Interface version 5 or newer required to support this verison of Vulkan (Policy #LDP_DRIVER_7) @@ -97,6 +95,10 @@ describe('lib/exec/spawn', function () { at Initialize (../../third_party/dawn/src/dawn/native/vulkan/BackendVk.cpp:344) at Create (../../third_party/dawn/src/dawn/native/vulkan/BackendVk.cpp:266) at operator() (../../third_party/dawn/src/dawn/native/vulkan/BackendVk.cpp:521) + + [78887:1023/114920.074882:ERROR:debug_utils.cc(14)] Hit debug scenario: 4 + + [18489:0822/130231.159571:ERROR:gl_display.cc(497)] EGL Driver message (Error) eglQueryDeviceAttribEXT: Bad attribute. ` const lines = _ diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index ab1dba589fe2..07b95291c365 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -70,18 +70,10 @@ declare namespace Cypress { strategy: 'file' | 'http' origin: string fileServer: string | null - props: Record - visiting: string + props: Record | null } interface Backend { - /** - * Firefox only: Force Cypress to run garbage collection routines. - * No-op if not running in Firefox. - * - * @see https://on.cypress.io/firefox-gc-issue - */ - (task: 'firefox:force:gc'): Promise (task: 'net', eventName: string, frame: any): Promise } @@ -125,11 +117,7 @@ declare namespace Cypress { */ warning?: string /** - * The minimum majorVersion of this browser supported by Cypress. - */ - minSupportedVersion?: number - /** - * If `true`, this browser is too old to be supported by Cypress. + * If `true`, this browser version is not supported in Cypress. */ unsupportedVersion?: boolean } @@ -3114,24 +3102,16 @@ declare namespace Cypress { */ experimentalModifyObstructiveThirdPartyCode: boolean /** - * Disables setting document.domain to the applications super domain on injection. - * This experiment is to be used for sites that do not work with setting document.domain - * due to cross-origin issues. Enabling this option no longer allows for default subdomain - * navigations, and will require the use of cy.origin(). This option takes an array of - * strings/string globs. - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/domain - * @see https://on.cypress.io/experiments#Experimental-Skip-Domain-Injection - * @default null - */ - experimentalSkipDomainInjection: string[] | null - /** - * Allows for just-in-time compiling of a component test, which will only compile assets related to the component. - * This results in a smaller bundle under test, reducing resource constraints on a given machine. This option is recommended - * for users with large component testing projects and those who are running into webpack 'chunk load error' issues. - * Supported for vite and webpack. For component testing only. - * @see https://on.cypress.io/experiments#Configuration + * Enables setting document.domain to the superdomain on code injection. This option is + * disabled by default. Enabling this option allows for navigating between subdomains in + * the same test without the use of cy.origin(). Setting document.domain is deprecated in Chrome. + * Enabling this may result in incompatibilities with sites that leverage origin-agent-cluster + * headers. Enabling this when a browser does not support setting document.domain will not result + * in the browser allowing document.domain to be set. In these cases, this configuration option + * must be set to false, to allow cy.origin() to be used on subdomains. + * @default false */ - experimentalJustInTimeCompile: boolean + injectDocumentDomain: boolean /** * Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement algorithm. * @default false @@ -3152,6 +3132,14 @@ declare namespace Cypress { * @default false */ experimentalMemoryManagement: boolean + /** + * Allows for just-in-time compiling of a component test, which will only compile assets related to the component. + * This results in a smaller bundle under test, reducing resource constraints on a given machine. This option is recommended + * for users with large component testing projects and those who are running into webpack 'chunk load error' issues. + * Supported for webpack-dev-server only. For component testing only. + * @see https://on.cypress.io/experiments#Configuration + */ + justInTimeCompile: boolean /** * Number of times to retry a failed test. * If a number is set, tests will retry in both runMode and openMode. @@ -3188,10 +3176,6 @@ declare namespace Cypress { * The user agent the browser sends in all request headers. */ userAgent: null | string - /** - * Polyfills `window.fetch` to enable Network spying and stubbing - */ - experimentalFetchPolyfill: boolean /** * Override default config options for Component Testing runner. @@ -3360,19 +3344,19 @@ declare namespace Cypress { interface CypressComponentDependency { /** * Unique identifier. - * @example 'reactscripts' + * @example 'react' */ type: string /** * Name to display in the user interface. - * @example "React Scripts" + * @example "React.js" */ name: string /** * Package name on npm. - * @example react-scripts + * @example react */ package: string @@ -3383,21 +3367,20 @@ declare namespace Cypress { * * @example `react` * @example `react@18` - * @example `react-scripts` */ installer: string /** * Description shown in UI. It is recommended to use the same one the package uses on npm. - * @example 'Create React apps with no build configuration' + * @example 'A JavaScript library for building user interfaces' */ description: string /** * Minimum version supported. Should conform to Semantic Versioning as used in `package.json`. * @see https://docs.npmjs.com/cli/v9/configuring-npm/package-json#dependencies - * @example '^=4.0.0 || ^=5.0.0' - * @example '^2.0.0' + * @example '^=17.0.0 || ^=8.0.0' + * @example '^4.0.0' */ minVersion: string } @@ -3406,21 +3389,21 @@ declare namespace Cypress { /** * A semantic, unique identifier. * Must begin with `cypress-ct-` or `@org/cypress-ct-` for third party implementations. - * @example 'reactscripts' + * @example 'react' * @example 'nextjs' * @example 'cypress-ct-solid-js' */ type: string /** - * Used as the flag for `getPreset` for meta framworks, such as finding the webpack config for CRA, Angular, etc. + * Used as the flag for `getPreset` for meta frameworks, such as finding the webpack config for CRA, Angular, etc. * It is also the name of the string added to `cypress.config` * * @example * export default { * component: { * devServer: { - * framework: 'create-react-app' // can be 'next', 'create-react-app', etc etc. + * framework: 'react' // can be 'next', 'vue', etc etc. * } * } * } @@ -3435,7 +3418,7 @@ declare namespace Cypress { /** * Name displayed in Launchpad when doing initial setup. * @example 'Solid.js' - * @example 'Create React App' + * @example 'React.js' */ name: string @@ -3465,12 +3448,12 @@ declare namespace Cypress { dependencies: (bundler: 'webpack' | 'vite', projectPath: string) => Promise /** - * This is used interally by Cypress for the "Create From Component" feature. + * This is used internally by Cypress for the "Create From Component" feature. */ codeGenFramework?: 'react' | 'vue' | 'svelte' | 'angular' /** - * This is used interally by Cypress for the "Create From Component" feature. + * This is used internally by Cypress for the "Create From Component" feature. * @example '*.{js,jsx,tsx}' */ glob?: string @@ -3536,7 +3519,7 @@ declare namespace Cypress { type DevServerConfigOptions = { bundler: 'webpack' - framework: 'react' | 'vue' | 'vue-cli' | 'nuxt' | 'create-react-app' | 'next' | 'svelte' + framework: 'react' | 'vue' | 'next' | 'svelte' webpackConfig?: ConfigHandler> } | { bundler: 'vite' diff --git a/cli/types/tests/cypress-npm-api-test.ts b/cli/types/tests/cypress-npm-api-test.ts index bea7236891ca..7d4c8d80970e 100644 --- a/cli/types/tests/cypress-npm-api-test.ts +++ b/cli/types/tests/cypress-npm-api-test.ts @@ -104,35 +104,6 @@ const componentConfigVueWebpack: Cypress.ConfigOptions = { } } -const componentConfigVueCliWebpack: Cypress.ConfigOptions = { - component: { - devServer: { - bundler: 'webpack', - framework: 'vue-cli', - webpackConfig: {} - } - } -} - -const componentConfigNuxtWebpack: Cypress.ConfigOptions = { - component: { - devServer: { - bundler: 'webpack', - framework: 'nuxt', - webpackConfig: {} - } - } -} - -const componentConfigCRAWebpack: Cypress.ConfigOptions = { - component: { - devServer: { - bundler: 'webpack', - framework: 'create-react-app', - } - } -} - const componentConfigViteReact: Cypress.ConfigOptions = { component: { devServer: { diff --git a/docker-compose.yml b/docker-compose.yml index 3f3c200c3658..9de8115e0a7d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,7 +31,7 @@ services: - .:/opt/cypress ci: # This should mirror the image used in workflows.yml - image: cypress/browsers-internal:node18.17.1-chrome118-ff115 + image: cypress/browsers-internal:node20.18.1-bullseye-chrome131-ff133 ports: - 5566:5566 - 5567:5567 diff --git a/npm/angular-signals/.eslintignore b/npm/angular-signals/.eslintignore deleted file mode 100644 index 79afe972da7d..000000000000 --- a/npm/angular-signals/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -**/dist -**/*.d.ts -**/package-lock.json -**/tsconfig.json -**/cypress/fixtures \ No newline at end of file diff --git a/npm/angular-signals/.eslintrc b/npm/angular-signals/.eslintrc deleted file mode 100644 index f044f320923e..000000000000 --- a/npm/angular-signals/.eslintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "plugins": [ - "cypress" - ], - "extends": [ - "plugin:@cypress/dev/tests" - ] -} diff --git a/npm/angular-signals/.npmignore b/npm/angular-signals/.npmignore deleted file mode 100644 index d4372c984ed9..000000000000 --- a/npm/angular-signals/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -examples -src -cypress \ No newline at end of file diff --git a/npm/angular-signals/.releaserc.js b/npm/angular-signals/.releaserc.js deleted file mode 100644 index 17d3bb871472..000000000000 --- a/npm/angular-signals/.releaserc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - ...require('../../.releaserc'), -} diff --git a/npm/angular-signals/CHANGELOG.md b/npm/angular-signals/CHANGELOG.md deleted file mode 100644 index e7244aa851c6..000000000000 --- a/npm/angular-signals/CHANGELOG.md +++ /dev/null @@ -1,6 +0,0 @@ -# @cypress/angular-signals-v1.0.0 (2024-07-02) - - -### Features - -* add Angular Signals CT Harness for Angular 17.2 and up for users to be able to use Angular Signals within their component tests ([#29621](https://github.com/cypress-io/cypress/issues/29621)) ([f2554f1](https://github.com/cypress-io/cypress/commit/f2554f12d6d1f438db898fbbc10a100ebff733ce)) diff --git a/npm/angular-signals/README.md b/npm/angular-signals/README.md deleted file mode 100644 index bbe66b417b27..000000000000 --- a/npm/angular-signals/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# @cypress/angular-signals - -Mount Angular components in the open source [Cypress.io](https://www.cypress.io/) test runner. This package is an extension of `@cypress/angular`, but with [signals](https://angular.dev/guide/signals) support. - -> **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [Angular Component Testing Docs](https://docs.cypress.io/guides/component-testing/angular/overview) for mounting Angular components. Installing and importing `mount` from `@cypress/angular-signals` should only be done for advanced use-cases. - -## Development - -Run `yarn build` to compile and sync packages to the `cypress` cli package. - -## [Changelog](./CHANGELOG.md) diff --git a/npm/angular-signals/package.json b/npm/angular-signals/package.json deleted file mode 100644 index 104c1f5b21be..000000000000 --- a/npm/angular-signals/package.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "name": "@cypress/angular-signals", - "version": "0.0.0-development", - "description": "Test Angular Components using Signals with Cypress", - "main": "dist/index.js", - "scripts": { - "prebuild": "rimraf dist", - "build": "rollup -c rollup.config.mjs", - "postbuild": "node ../../scripts/sync-exported-npm-with-cli.js", - "check-ts": "tsc --noEmit", - "dev": "rollup -c rollup.config.mjs -w", - "lint": "eslint --ext .js,.ts,.json, ." - }, - "dependencies": {}, - "devDependencies": { - "@angular/common": "^17.2.0", - "@angular/core": "^17.2.0", - "@angular/platform-browser-dynamic": "^17.2.0", - "@cypress/mount-utils": "0.0.0-development", - "typescript": "~5.4.5", - "zone.js": "~0.14.6" - }, - "peerDependencies": { - "@angular/common": ">=17.2", - "@angular/core": ">=17.2", - "@angular/platform-browser-dynamic": ">=17.2", - "rxjs": ">=7.5.0", - "zone.js": ">=0.13.0" - }, - "files": [ - "dist" - ], - "types": "dist/index.d.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/cypress-io/cypress.git" - }, - "homepage": "https://github.com/cypress-io/cypress/blob/develop/npm/angular-signals/#readme", - "bugs": "https://github.com/cypress-io/cypress/issues/new?assignees=&labels=npm%3A%20%40cypress%2Fangular&template=1-bug-report.md&title=", - "keywords": [ - "angular", - "cypress", - "cypress-io", - "test", - "testing" - ], - "contributors": [ - { - "name": "Bill Glesias", - "social": "@atofstryker" - } - ], - "module": "dist/index.js", - "publishConfig": { - "access": "public" - }, - "nx": { - "targets": { - "build": { - "outputs": [ - "{workspaceRoot}/cli/angular-signals" - ] - } - } - }, - "standard": { - "globals": [ - "Cypress", - "cy", - "expect" - ] - } -} diff --git a/npm/angular-signals/rollup.config.mjs b/npm/angular-signals/rollup.config.mjs deleted file mode 100644 index 4ef324cfd1a3..000000000000 --- a/npm/angular-signals/rollup.config.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { createEntries } from '@cypress/mount-utils/create-rollup-entry.mjs' - -const config = { - external: [ - '@angular/core', - '@angular/core/testing', - '@angular/common', - '@angular/platform-browser-dynamic/testing', - 'zone.js', - 'zone.js/testing', - ], -} - -export default createEntries({ formats: ['es'], input: 'src/index.ts', config }) diff --git a/npm/angular-signals/src/index.ts b/npm/angular-signals/src/index.ts deleted file mode 100644 index 1af962e1d530..000000000000 --- a/npm/angular-signals/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mount' diff --git a/npm/angular-signals/src/mount.ts b/npm/angular-signals/src/mount.ts deleted file mode 100644 index 1c039f2970be..000000000000 --- a/npm/angular-signals/src/mount.ts +++ /dev/null @@ -1,554 +0,0 @@ -import 'zone.js' - -/** - * @hack fixes "Mocha has already been patched with Zone" error. - */ -// @ts-ignore -window.Mocha['__zone_patch__'] = false -import 'zone.js/testing' - -import { CommonModule } from '@angular/common' -import { Component, ErrorHandler, EventEmitter, Injectable, SimpleChange, SimpleChanges, Type, OnChanges, Injector, InputSignal, WritableSignal, signal } from '@angular/core' -import { toObservable } from '@angular/core/rxjs-interop' -import { - ComponentFixture, - getTestBed, - TestModuleMetadata, - TestBed, - TestComponentRenderer, -} from '@angular/core/testing' -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting, -} from '@angular/platform-browser-dynamic/testing' -import { - setupHooks, - getContainerEl, -} from '@cypress/mount-utils' -import type { Subscription } from 'rxjs' - -/** - * Additional module configurations needed while mounting the component, like - * providers, declarations, imports and even component @Inputs() - * - * @interface MountConfig - * @see https://angular.io/api/core/testing/TestModuleMetadata - */ -export interface MountConfig extends TestModuleMetadata { - /** - * @memberof MountConfig - * @description flag to automatically create a cy.spy() for every component @Output() property - * @example - * export class ButtonComponent { - * @Output clicked = new EventEmitter() - * } - * - * cy.mount(ButtonComponent, { autoSpyOutputs: true }) - * cy.get('@clickedSpy).should('have.been.called') - */ - autoSpyOutputs?: boolean - - /** - * @memberof MountConfig - * @description flag defaulted to true to automatically detect changes in your components - */ - autoDetectChanges?: boolean - /** - * @memberof MountConfig - * @example - * import { ButtonComponent } from 'button/button.component' - * it('renders a button with Save text', () => { - * cy.mount(ButtonComponent, { componentProperties: { text: 'Save' }}) - * cy.get('button').contains('Save') - * }) - * - * it('renders a button with a cy.spy() replacing EventEmitter', () => { - * cy.mount(ButtonComponent, { - * componentProperties: { - * clicked: cy.spy().as('mySpy) - * } - * }) - * cy.get('button').click() - * cy.get('@mySpy').should('have.been.called') - * }) - */ - // allow InputSignals to be type primitive and WritableSignal for type compliance - componentProperties?: Partial<{ [P in keyof T]: T[P] extends InputSignal ? InputSignal | WritableSignal | V : T[P]}> -} - -let activeFixture: ComponentFixture | null = null -let activeInternalSubscriptions: Subscription[] = [] - -function cleanup () { - // Not public, we need to call this to remove the last component from the DOM - try { - (getTestBed() as any).tearDownTestingModule() - } catch (e) { - const notSupportedError = new Error(`Failed to teardown component. The version of Angular you are using may not be officially supported.`) - - ;(notSupportedError as any).docsUrl = 'https://on.cypress.io/component-framework-configuration' - throw notSupportedError - } - - // clean up internal subscriptions if any exist. We use this for two-way data binding for - // signal() models - activeInternalSubscriptions.forEach((subscription) => { - subscription.unsubscribe() - }) - - getTestBed().resetTestingModule() - activeFixture = null - activeInternalSubscriptions = [] -} - -/** - * Type that the `mount` function returns - * @type MountResponse - */ -export type MountResponse = { - /** - * Fixture for debugging and testing a component. - * - * @memberof MountResponse - * @see https://angular.io/api/core/testing/ComponentFixture - */ - fixture: ComponentFixture - - /** - * The instance of the root component class - * - * @memberof MountResponse - * @see https://angular.io/api/core/testing/ComponentFixture#componentInstance - */ - component: T -}; - -// 'zone.js/testing' is not properly aliasing `it.skip` but it does provide `xit`/`xspecify` -// Written up under https://github.com/angular/angular/issues/46297 but is not seeing movement -// so we'll patch here pending a fix in that library -// @ts-ignore Ignore so that way we can bypass semantic error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. -globalThis.it.skip = globalThis.xit - -@Injectable() -class CypressAngularErrorHandler implements ErrorHandler { - handleError (error: Error): void { - throw error - } -} - -/** - * Bootstraps the TestModuleMetaData passed to the TestBed - * - * @param {Type} component Angular component being mounted - * @param {MountConfig} config TestBed configuration passed into the mount function - * @returns {MountConfig} MountConfig - */ -function bootstrapModule ( - component: Type, - config: MountConfig, -): MountConfig { - const { componentProperties, ...testModuleMetaData } = config - - if (!testModuleMetaData.declarations) { - testModuleMetaData.declarations = [] - } - - if (!testModuleMetaData.imports) { - testModuleMetaData.imports = [] - } - - if (!testModuleMetaData.providers) { - testModuleMetaData.providers = [] - } - - // Replace default error handler since it will swallow uncaught exceptions. - // We want these to be uncaught so Cypress catches it and fails the test - testModuleMetaData.providers.push({ - provide: ErrorHandler, - useClass: CypressAngularErrorHandler, - }) - - // check if the component is a standalone component - if ((component as any).ɵcmp?.standalone) { - testModuleMetaData.imports.push(component) - } else { - testModuleMetaData.declarations.push(component) - } - - if (!testModuleMetaData.imports.includes(CommonModule)) { - testModuleMetaData.imports.push(CommonModule) - } - - return testModuleMetaData -} - -@Injectable() -export class CypressTestComponentRenderer extends TestComponentRenderer { - override insertRootElement (rootElId: string) { - this.removeAllRootElements() - - const rootElement = getContainerEl() - - rootElement.setAttribute('id', rootElId) - } - - override removeAllRootElements () { - getContainerEl().innerHTML = '' - } -} - -/** - * Initializes the TestBed - * - * @param {Type | string} component Angular component being mounted or its template - * @param {MountConfig} config TestBed configuration passed into the mount function - * @returns {Type} componentFixture - */ -function initTestBed ( - component: Type | string, - config: MountConfig, -): Type { - const componentFixture = createComponentFixture(component) as Type - - getTestBed().configureTestingModule({ - ...bootstrapModule(componentFixture, config), - }) - - getTestBed().overrideProvider(TestComponentRenderer, { useValue: new CypressTestComponentRenderer() }) - - return componentFixture -} - -@Component({ selector: 'cy-wrapper-component', template: '' }) -class WrapperComponent { } - -/** - * Returns the Component if Type or creates a WrapperComponent - * - * @param {Type | string} component The component you want to create a fixture of - * @returns {Type | WrapperComponent} - */ -function createComponentFixture ( - component: Type | string, -): Type { - if (typeof component === 'string') { - // getTestBed().overrideTemplate is available in v14+ - // The static TestBed.overrideTemplate is available across versions - TestBed.overrideTemplate(WrapperComponent, component) - - return WrapperComponent - } - - return component -} - -/** - * Creates the ComponentFixture - * - * @param {Type} component Angular component being mounted - * @param {MountConfig} config MountConfig - - * @returns {ComponentFixture} ComponentFixture - */ -function setupFixture ( - component: Type, - config: MountConfig, -): ComponentFixture { - const fixture = getTestBed().createComponent(component) - - setupComponent(config, fixture) - - fixture.whenStable().then(() => { - fixture.autoDetectChanges(config.autoDetectChanges ?? true) - }) - - return fixture -} - -// Best known way to currently detect whether or not a function is a signal is if the signal symbol exists. -// From there, we can take our best guess based on what exists on the object itself. -// @see https://github.com/cypress-io/cypress/issues/29731. -function isSignal (prop: any): boolean { - try { - const symbol = Object.getOwnPropertySymbols(prop).find((symbol) => symbol.toString() === 'Symbol(SIGNAL)') - - return !!symbol - } catch (e) { - // likely a primitive type, object, array, or something else (i.e. not a signal). - // We can return false here. - return false - } -} - -// currently not a great way to detect if a function is an InputSignal. -// @see https://github.com/cypress-io/cypress/issues/29731. -function isInputSignal (prop: any): boolean { - return isSignal(prop) && typeof prop === 'function' && prop['name'] === 'inputValueFn' -} - -// currently not a great way to detect if a function is a Model Signal. -// @see https://github.com/cypress-io/cypress/issues/29731. -function isModelSignal (prop: any): boolean { - return isSignal(prop) && isWritableSignal(prop) && typeof prop.subscribe === 'function' -} - -// currently not a great way to detect if a function is a Writable Signal. -// @see https://github.com/cypress-io/cypress/issues/29731. -function isWritableSignal (prop: any): boolean { - return isSignal(prop) && typeof prop === 'function' && typeof prop.set === 'function' -} - -function convertPropertyToSignalIfApplicable (propValue: any, componentValue: any, injector: Injector) { - const isComponentValueAnInputSignal = isInputSignal(componentValue) - const isComponentValueAModelSignal = isModelSignal(componentValue) - let convertedValueIfApplicable = propValue - - // If the component has the property defined as an InputSignal, we need to detect whether a non signal value or not was passed into the component as a prop - // and attempt to merge the value in correctly. - // We don't want to expose the primitive created signal as it should really be one-way binding from within the component. - // However, to make CT testing easier, a user can technically pass in a signal to an input component and assert on the signal itself and pass in updates - // down to the component as 1 way binding is supported by the test harness - if (isComponentValueAnInputSignal) { - const isPassedInValueNotASignal = !isSignal(propValue) - - if (isPassedInValueNotASignal) { - // Input signals require an injection context to set initial values. - // Because of this, we cannot create them outside the scope of the component. - // Options for input signals also don't allow the passing of an injection contexts, so in order to work around this, - // we convert the non signal input passed into the input to a writable signal - convertedValueIfApplicable = signal(propValue) - } - - // If the component has the property defined as a ModelSignal, we need to detect whether a signal value or not was passed into the component as a prop. - // If a non signal property is passed into the component model (primitive, object, array, etc), we need to set the model to that value and propagate changes of that model through the output spy. - // Since the non signal type likely lives outside the context of Angular, the non signal type will NOT be updated outside of this context. Instead, the output spy will allow you - // to see this change. - // If the value passed into the property is in fact a signal, we need to set up two-way binding between the signals to make sure changes from one propagate to the other. - } else if (isComponentValueAModelSignal) { - const isPassedInValueLikelyARegularSignal = isWritableSignal(propValue) - - // if the value passed into the component is a signal, set up two-way binding - if (isPassedInValueLikelyARegularSignal) { - // update the passed in value with the models updates - componentValue.subscribe((value: any) => { - propValue.set(value) - }) - - // update the model signal with the properties updates - const convertedToObservable = toObservable(propValue, { - injector, - }) - - // push the subscription into an array to be cleaned up at the end of the test - // to prevent a memory leak - activeInternalSubscriptions.push( - convertedToObservable.subscribe((value) => { - componentValue.set(value) - }), - ) - } else { - // it's a non signal type, set it as we only need to handle updating the model signal and emit changes on this through the output spy. - componentValue.set(propValue) - - convertedValueIfApplicable = componentValue - } - } - - return convertedValueIfApplicable -} - -// In the case of signals, if we need to create an output spy, we need to check first whether or not a user has one defined first or has it created through -// autoSpyOutputs. If so, we need to subscribe to the writable signal to push updates into the event emitter. We do NOT observe input signals and output spies will not -// work for input signals. -function detectAndRegisterOutputSpyToSignal (config: MountConfig, component: { [key: string]: any } & Partial, key: string, injector: Injector): void { - if (config.componentProperties) { - const expectedChangeKey = `${key}Change` - let changeKeyIfExists = !!Object.keys(config.componentProperties).find((componentKey) => componentKey === expectedChangeKey) - - // since spies do NOT make change handlers by default, similar to the Output() decorator, we need to create the spy and subscribe to the signal - if (!changeKeyIfExists && config.autoSpyOutputs) { - component[expectedChangeKey] = createOutputSpy(`${expectedChangeKey}Spy`) - changeKeyIfExists = true - } - - if (changeKeyIfExists) { - const componentValue = component[key] - - // if the user passed in a change key or we created one due to config.autoSpyOutputs being set to true for a given signal, - // we will create a subscriber that will emit an event every time the value inside the signal changes. We only do this - // if the signal is writable and not an input signal. - if (isWritableSignal(componentValue) && !isInputSignal(componentValue)) { - toObservable(componentValue, { - injector, - }).subscribe((value) => { - component[expectedChangeKey]?.emit(value) - }) - } - } - } -} - -/** - * Gets the componentInstance and Object.assigns any componentProperties() passed in the MountConfig - * - * @param {MountConfig} config TestBed configuration passed into the mount function - * @param {ComponentFixture} fixture Fixture for debugging and testing a component. - * @returns {T} Component being mounted - */ -function setupComponent ( - config: MountConfig, - fixture: ComponentFixture, -): void { - let component = fixture.componentInstance as unknown as { [key: string]: any } & Partial - const injector = fixture.componentRef.injector - - if (config?.componentProperties) { - // convert primitives to signals if passed in type is a primitive but expected type is signal - // a bit of magic. need to move to another function - Object.keys(component).forEach((key) => { - // only assign props if they are passed into the component - if (config?.componentProperties?.hasOwnProperty(key)) { - // @ts-expect-error - const passedInValue = config?.componentProperties[key] - - const componentValue = component[key] - - // @ts-expect-error - config.componentProperties[key] = convertPropertyToSignalIfApplicable(passedInValue, componentValue, injector) - detectAndRegisterOutputSpyToSignal(config, component, key, injector) - } - }) - - component = Object.assign(component, config.componentProperties) - } - - if (config.autoSpyOutputs) { - Object.keys(component).forEach((key) => { - const property = component[key] - - if (property instanceof EventEmitter) { - component[key] = createOutputSpy(`${key}Spy`) - } - }) - } - - // Manually call ngOnChanges when mounting components using the class syntax. - // This is necessary because we are assigning input values to the class directly - // on mount and therefore the ngOnChanges() lifecycle is not triggered. - if (component.ngOnChanges && config.componentProperties) { - const { componentProperties } = config - - const simpleChanges: SimpleChanges = Object.entries(componentProperties).reduce((acc, [key, value]) => { - acc[key] = new SimpleChange(null, value, true) - - return acc - }, {} as {[key: string]: SimpleChange}) - - if (Object.keys(componentProperties).length > 0) { - component.ngOnChanges(simpleChanges) - } - } -} - -/** - * Mounts an Angular component inside Cypress browser - * - * @param component Angular component being mounted or its template - * @param config configuration used to configure the TestBed - * @example - * import { mount } from '@cypress/angular-signals' - * import { StepperComponent } from './stepper.component' - * import { MyService } from 'services/my.service' - * import { SharedModule } from 'shared/shared.module'; - * it('mounts', () => { - * mount(StepperComponent, { - * providers: [MyService], - * imports: [SharedModule] - * }) - * cy.get('[data-cy=increment]').click() - * cy.get('[data-cy=counter]').should('have.text', '1') - * }) - * - * // or - * - * it('mounts with template', () => { - * mount('', { - * declarations: [StepperComponent], - * }) - * }) - * - * @see {@link https://on.cypress.io/mounting-angular} for more details. - * - * @returns A component and component fixture - */ -export function mount ( - component: Type | string, - config: MountConfig = { }, -): Cypress.Chainable> { - // Remove last mounted component if cy.mount is called more than once in a test - if (activeFixture) { - cleanup() - } - - const componentFixture = initTestBed(component, config) - - activeFixture = setupFixture(componentFixture, config) - - const mountResponse: MountResponse = { - fixture: activeFixture, - component: activeFixture.componentInstance, - } - - const logMessage = typeof component === 'string' ? 'Component' : componentFixture.name - - Cypress.log({ - name: 'mount', - message: logMessage, - consoleProps: () => ({ result: mountResponse }), - }) - - return cy.wrap(mountResponse, { log: false }) -} - -/** - * Creates a new Event Emitter and then spies on it's `emit` method - * - * @param {string} alias name you want to use for your cy.spy() alias - * @returns EventEmitter - * @example - * import { StepperComponent } from './stepper.component' - * import { mount, createOutputSpy } from '@cypress/angular-signals' - * - * it('Has spy', () => { - * mount(StepperComponent, { componentProperties: { change: createOutputSpy('changeSpy') } }) - * cy.get('[data-cy=increment]').click() - * cy.get('@changeSpy').should('have.been.called') - * }) - * - * // Or for use with Angular Signals following the output nomenclature. - * // see https://v17.angular.io/guide/model-inputs#differences-between-model-and-input/ - * - * it('Has spy', () => { - * mount(StepperComponent, { componentProperties: { count: signal(0), countChange: createOutputSpy('countChange') } }) - * cy.get('[data-cy=increment]').click() - * cy.get('@countChange').should('have.been.called') - * }) - */ -export const createOutputSpy = (alias: string) => { - const emitter = new EventEmitter() - - cy.spy(emitter, 'emit').as(alias) - - return emitter as any -} - -// Only needs to run once, we reset before each test -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting(), - { - teardown: { destroyAfterEach: false }, - }, -) - -setupHooks(cleanup) diff --git a/npm/angular-signals/tsconfig.json b/npm/angular-signals/tsconfig.json deleted file mode 100644 index a73e01dcecc9..000000000000 --- a/npm/angular-signals/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "experimentalDecorators": true, - "target": "es2020", - "module": "es2020", - "skipLibCheck": true, - "lib": [ - "ESNext", - "DOM" - ], - "allowJs": true, - "declaration": true, - "outDir": "dist", - "strict": true, - "baseUrl": "./", - "types": [ - "cypress" - ], - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "moduleResolution": "node", - "noPropertyAccessFromIndexSignature": true, - }, - "include": ["src/**/*.*"], - "exclude": ["src/**/*-spec.*"] -} diff --git a/npm/angular/README.md b/npm/angular/README.md index 2e75bfe830b1..0ca548725828 100644 --- a/npm/angular/README.md +++ b/npm/angular/README.md @@ -3,6 +3,11 @@ Mount Angular components in the open source [Cypress.io](https://www.cypress.io/) test runner > **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [Angular Component Testing Docs](https://docs.cypress.io/guides/component-testing/angular/overview) for mounting Angular components. Installing and importing `mount` from `@cypress/angular` should only be done for advanced use-cases. + +## Requirements + +- Angular 17.2.0+ (Cypress 13 and under supports Angular 13 - 16) + ## Development Run `yarn build` to compile and sync packages to the `cypress` cli package. diff --git a/npm/angular/package.json b/npm/angular/package.json index 6917b8c30358..a2b02d0cecf4 100644 --- a/npm/angular/package.json +++ b/npm/angular/package.json @@ -1,29 +1,32 @@ { "name": "@cypress/angular", "version": "0.0.0-development", - "description": "Test Angular Components using Cypress", + "description": "Test Angular Components with Cypress", "main": "dist/index.js", "scripts": { "prebuild": "rimraf dist", "build": "rollup -c rollup.config.mjs", "postbuild": "node ../../scripts/sync-exported-npm-with-cli.js", "check-ts": "tsc --noEmit", + "dev": "rollup -c rollup.config.mjs -w", "lint": "eslint --ext .js,.ts,.json, ." }, "dependencies": {}, "devDependencies": { - "@angular/common": "^14.2.0", - "@angular/core": "^14.2.0", - "@angular/platform-browser-dynamic": "^14.2.0", + "@angular/common": "^17.2.0", + "@angular/core": "^17.2.0", + "@angular/platform-browser-dynamic": "^17.2.0", "@cypress/mount-utils": "0.0.0-development", + "rollup": "^4.24.4", "typescript": "~5.4.5", - "zone.js": "~0.11.4" + "zone.js": "~0.14.6" }, "peerDependencies": { - "@angular/common": ">=13", - "@angular/core": ">=13", - "@angular/platform-browser-dynamic": ">=13", - "zone.js": ">=0.11.0" + "@angular/common": ">=17.2", + "@angular/core": ">=17.2", + "@angular/platform-browser-dynamic": ">=17.2", + "rxjs": ">=7.5.0", + "zone.js": ">=0.13.0" }, "files": [ "dist" @@ -44,6 +47,10 @@ "testing" ], "contributors": [ + { + "name": "Bill Glesias", + "social": "@atofstryker" + }, { "name": "Jordan Powell", "social": "@jordanpowell88" diff --git a/npm/angular/src/mount.ts b/npm/angular/src/mount.ts index 0c923456a1df..0774d02ecc51 100644 --- a/npm/angular/src/mount.ts +++ b/npm/angular/src/mount.ts @@ -8,7 +8,8 @@ window.Mocha['__zone_patch__'] = false import 'zone.js/testing' import { CommonModule } from '@angular/common' -import { Component, ErrorHandler, EventEmitter, Injectable, SimpleChange, SimpleChanges, Type, OnChanges } from '@angular/core' +import { Component, ErrorHandler, EventEmitter, Injectable, SimpleChange, SimpleChanges, Type, OnChanges, Injector, InputSignal, WritableSignal, signal } from '@angular/core' +import { toObservable } from '@angular/core/rxjs-interop' import { ComponentFixture, getTestBed, @@ -24,6 +25,7 @@ import { setupHooks, getContainerEl, } from '@cypress/mount-utils' +import type { Subscription } from 'rxjs' /** * Additional module configurations needed while mounting the component, like @@ -70,10 +72,12 @@ export interface MountConfig extends TestModuleMetadata { * cy.get('@mySpy').should('have.been.called') * }) */ - componentProperties?: Partial<{ [P in keyof T]: T[P] }> + // allow InputSignals to be type primitive and WritableSignal for type compliance + componentProperties?: Partial<{ [P in keyof T]: T[P] extends InputSignal ? InputSignal | WritableSignal | V : T[P]}> } let activeFixture: ComponentFixture | null = null +let activeInternalSubscriptions: Subscription[] = [] function cleanup () { // Not public, we need to call this to remove the last component from the DOM @@ -86,8 +90,15 @@ function cleanup () { throw notSupportedError } + // clean up internal subscriptions if any exist. We use this for two-way data binding for + // signal() models + activeInternalSubscriptions.forEach((subscription) => { + subscription.unsubscribe() + }) + getTestBed().resetTestingModule() activeFixture = null + activeInternalSubscriptions = [] } /** @@ -208,7 +219,9 @@ function initTestBed ( return componentFixture } -@Component({ selector: 'cy-wrapper-component', template: '' }) +// if using the Wrapper Component (template strings), the component itself cannot be +// a standalone component +@Component({ selector: 'cy-wrapper-component', template: '', standalone: false }) class WrapperComponent { } /** @@ -249,11 +262,139 @@ function setupFixture ( fixture.whenStable().then(() => { fixture.autoDetectChanges(config.autoDetectChanges ?? true) + }).catch((e) => { + // If this promise does not settle in Angular 19 it is rejected + // https://github.com/angular/angular/blob/main/CHANGELOG.md#1900-2024-11-19 + // eslint-disable-next-line no-console + console.error(e) }) return fixture } +// Best known way to currently detect whether or not a function is a signal is if the signal symbol exists. +// From there, we can take our best guess based on what exists on the object itself. +// @see https://github.com/cypress-io/cypress/issues/29731. +function isSignal (prop: any): boolean { + try { + const symbol = Object.getOwnPropertySymbols(prop).find((symbol) => symbol.toString() === 'Symbol(SIGNAL)') + + return !!symbol + } catch (e) { + // likely a primitive type, object, array, or something else (i.e. not a signal). + // We can return false here. + return false + } +} + +// currently not a great way to detect if a function is an InputSignal. +// @see https://github.com/cypress-io/cypress/issues/29731. +function isInputSignal (prop: any): boolean { + return isSignal(prop) && typeof prop === 'function' && prop['name'] === 'inputValueFn' +} + +// currently not a great way to detect if a function is a Model Signal. +// @see https://github.com/cypress-io/cypress/issues/29731. +function isModelSignal (prop: any): boolean { + return isSignal(prop) && isWritableSignal(prop) && typeof prop.subscribe === 'function' +} + +// currently not a great way to detect if a function is a Writable Signal. +// @see https://github.com/cypress-io/cypress/issues/29731. +function isWritableSignal (prop: any): boolean { + return isSignal(prop) && typeof prop === 'function' && typeof prop.set === 'function' +} + +function convertPropertyToSignalIfApplicable (propValue: any, componentValue: any, injector: Injector) { + const isComponentValueAnInputSignal = isInputSignal(componentValue) + const isComponentValueAModelSignal = isModelSignal(componentValue) + let convertedValueIfApplicable = propValue + + // If the component has the property defined as an InputSignal, we need to detect whether a non signal value or not was passed into the component as a prop + // and attempt to merge the value in correctly. + // We don't want to expose the primitive created signal as it should really be one-way binding from within the component. + // However, to make CT testing easier, a user can technically pass in a signal to an input component and assert on the signal itself and pass in updates + // down to the component as 1 way binding is supported by the test harness + if (isComponentValueAnInputSignal) { + const isPassedInValueNotASignal = !isSignal(propValue) + + if (isPassedInValueNotASignal) { + // Input signals require an injection context to set initial values. + // Because of this, we cannot create them outside the scope of the component. + // Options for input signals also don't allow the passing of an injection contexts, so in order to work around this, + // we convert the non signal input passed into the input to a writable signal + convertedValueIfApplicable = signal(propValue) + } + + // If the component has the property defined as a ModelSignal, we need to detect whether a signal value or not was passed into the component as a prop. + // If a non signal property is passed into the component model (primitive, object, array, etc), we need to set the model to that value and propagate changes of that model through the output spy. + // Since the non signal type likely lives outside the context of Angular, the non signal type will NOT be updated outside of this context. Instead, the output spy will allow you + // to see this change. + // If the value passed into the property is in fact a signal, we need to set up two-way binding between the signals to make sure changes from one propagate to the other. + } else if (isComponentValueAModelSignal) { + const isPassedInValueLikelyARegularSignal = isWritableSignal(propValue) + + // if the value passed into the component is a signal, set up two-way binding + if (isPassedInValueLikelyARegularSignal) { + // update the passed in value with the models updates + componentValue.subscribe((value: any) => { + propValue.set(value) + }) + + // update the model signal with the properties updates + const convertedToObservable = toObservable(propValue, { + injector, + }) + + // push the subscription into an array to be cleaned up at the end of the test + // to prevent a memory leak + activeInternalSubscriptions.push( + convertedToObservable.subscribe((value) => { + componentValue.set(value) + }), + ) + } else { + // it's a non signal type, set it as we only need to handle updating the model signal and emit changes on this through the output spy. + componentValue.set(propValue) + + convertedValueIfApplicable = componentValue + } + } + + return convertedValueIfApplicable +} + +// In the case of signals, if we need to create an output spy, we need to check first whether or not a user has one defined first or has it created through +// autoSpyOutputs. If so, we need to subscribe to the writable signal to push updates into the event emitter. We do NOT observe input signals and output spies will not +// work for input signals. +function detectAndRegisterOutputSpyToSignal (config: MountConfig, component: { [key: string]: any } & Partial, key: string, injector: Injector): void { + if (config.componentProperties) { + const expectedChangeKey = `${key}Change` + let changeKeyIfExists = !!Object.keys(config.componentProperties).find((componentKey) => componentKey === expectedChangeKey) + + // since spies do NOT make change handlers by default, similar to the Output() decorator, we need to create the spy and subscribe to the signal + if (!changeKeyIfExists && config.autoSpyOutputs) { + component[expectedChangeKey] = createOutputSpy(`${expectedChangeKey}Spy`) + changeKeyIfExists = true + } + + if (changeKeyIfExists) { + const componentValue = component[key] + + // if the user passed in a change key or we created one due to config.autoSpyOutputs being set to true for a given signal, + // we will create a subscriber that will emit an event every time the value inside the signal changes. We only do this + // if the signal is writable and not an input signal. + if (isWritableSignal(componentValue) && !isInputSignal(componentValue)) { + toObservable(componentValue, { + injector, + }).subscribe((value) => { + component[expectedChangeKey]?.emit(value) + }) + } + } + } +} + /** * Gets the componentInstance and Object.assigns any componentProperties() passed in the MountConfig * @@ -266,8 +407,25 @@ function setupComponent ( fixture: ComponentFixture, ): void { let component = fixture.componentInstance as unknown as { [key: string]: any } & Partial + const injector = fixture.componentRef.injector if (config?.componentProperties) { + // convert primitives to signals if passed in type is a primitive but expected type is signal + // a bit of magic. need to move to another function + Object.keys(component).forEach((key) => { + // only assign props if they are passed into the component + if (config?.componentProperties?.hasOwnProperty(key)) { + // @ts-expect-error + const passedInValue = config?.componentProperties[key] + + const componentValue = component[key] + + // @ts-expect-error + config.componentProperties[key] = convertPropertyToSignalIfApplicable(passedInValue, componentValue, injector) + detectAndRegisterOutputSpyToSignal(config, component, key, injector) + } + }) + component = Object.assign(component, config.componentProperties) } @@ -373,6 +531,15 @@ export function mount ( * cy.get('[data-cy=increment]').click() * cy.get('@changeSpy').should('have.been.called') * }) + * + * // Or for use with Angular Signals following the output nomenclature. + * // see https://v17.angular.io/guide/model-inputs#differences-between-model-and-input/ + * + * it('Has spy', () => { + * mount(StepperComponent, { componentProperties: { count: signal(0), countChange: createOutputSpy('countChange') } }) + * cy.get('[data-cy=increment]').click() + * cy.get('@countChange').should('have.been.called') + * }) */ export const createOutputSpy = (alias: string) => { const emitter = new EventEmitter() diff --git a/npm/cypress-schematic/README.md b/npm/cypress-schematic/README.md index 71be26beeaa1..f0c64ba2b929 100644 --- a/npm/cypress-schematic/README.md +++ b/npm/cypress-schematic/README.md @@ -31,7 +31,7 @@ ## Requirements -- Angular 14+ +- Angular 17.2.0+ (Cypress 13 and under supports Angular 13 - 16) ## Usage ⏯ diff --git a/npm/cypress-schematic/package.json b/npm/cypress-schematic/package.json index 43214a662e39..2af4628482e2 100644 --- a/npm/cypress-schematic/package.json +++ b/npm/cypress-schematic/package.json @@ -7,29 +7,28 @@ "build": "tsc -p tsconfig.json", "build:watch": "tsc -p tsconfig.json --watch", "lint": "eslint --ext .ts,.json, .", - "test": "mocha -r @packages/ts/register --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json src/**/*.spec.ts" + "test": "vitest run --no-file-parallelism" }, "dependencies": { - "jsonc-parser": "^3.0.0", - "rxjs": "~6.6.0" + "jsonc-parser": "^3.3.1", + "rxjs": "~7.8.1" }, "devDependencies": { - "@angular-devkit/architect": "^0.1402.1", - "@angular-devkit/core": "^14.2.1", - "@angular-devkit/schematics": "^14.2.1", - "@angular-devkit/schematics-cli": "^14.2.1", - "@angular/cli": "^14.2.1", - "@schematics/angular": "^14.2.1", - "@types/chai-enzyme": "0.6.7", + "@angular-devkit/architect": "^0.1802.11", + "@angular-devkit/core": "^18.2.11", + "@angular-devkit/schematics": "^18.2.11", + "@angular-devkit/schematics-cli": "^18.2.11", + "@angular/cli": "^18.2.11", + "@schematics/angular": "^18.2.11", + "@types/chai-enzyme": "0.6.13", "@types/mocha": "8.0.3", - "@types/node": "^18.17.5", - "chai": "4.2.0", - "mocha": "3.5.3", - "typescript": "~5.4.5" + "@types/node": "^20.16.0", + "typescript": "~5.4.5", + "vitest": "2.1.4" }, "peerDependencies": { - "@angular/cli": ">=14", - "@angular/core": ">=14" + "@angular/cli": ">=17.2", + "@angular/core": ">=17.2" }, "license": "MIT", "repository": { diff --git a/system-tests/projects/angular-cli-configured/src/assets/.gitkeep b/npm/cypress-schematic/projects/sandbox/src/.gitkeep similarity index 100% rename from system-tests/projects/angular-cli-configured/src/assets/.gitkeep rename to npm/cypress-schematic/projects/sandbox/src/.gitkeep diff --git a/system-tests/project-fixtures/nuxtjs2/cypress/support/component.js b/npm/cypress-schematic/projects/sandbox/src/fake-component.component.ts similarity index 100% rename from system-tests/project-fixtures/nuxtjs2/cypress/support/component.js rename to npm/cypress-schematic/projects/sandbox/src/fake-component.component.ts diff --git a/npm/cypress-schematic/src/ct.spec.ts b/npm/cypress-schematic/src/ct.spec.ts index 0e1929dd3be4..b0f84015ea7a 100644 --- a/npm/cypress-schematic/src/ct.spec.ts +++ b/npm/cypress-schematic/src/ct.spec.ts @@ -1,21 +1,10 @@ +import { describe, it, beforeEach, afterEach } from 'vitest' import Fixtures, { ProjectFixtureDir } from '@tooling/system-tests' import * as FixturesScaffold from '@tooling/system-tests/lib/dep-installer' import execa from 'execa' import path from 'path' import * as fs from 'fs-extra' -const scaffoldAngularProject = async (project: string) => { - const projectPath = Fixtures.projectPath(project) - - Fixtures.removeProject(project) - await Fixtures.scaffoldProject(project) - await FixturesScaffold.scaffoldProjectNodeModules({ project }) - await fs.remove(path.join(projectPath, 'cypress.config.ts')) - await fs.remove(path.join(projectPath, 'cypress')) - - return projectPath -} - const runCommandInProject = (command: string, projectPath: string) => { const [ex, ...args] = command.split(' ') @@ -35,29 +24,40 @@ const copyAngularMount = async (projectPath: string) => { const cypressSchematicPackagePath = path.join(__dirname, '..') -const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-14', 'angular-15'] +const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-18', 'angular-19'] -describe('ng add @cypress/schematic / e2e and ct', function () { - this.timeout(1000 * 60 * 5) +const timeout = 1000 * 60 * 5 +describe('ng add @cypress/schematic / e2e and ct', function () { for (const project of ANGULAR_PROJECTS) { - it('should install ct files with option and no component specs', async () => { - const projectPath = await scaffoldAngularProject(project) - - await runCommandInProject(`yarn add @cypress/schematic@file:${cypressSchematicPackagePath}`, projectPath) - await runCommandInProject('yarn ng add @cypress/schematic --e2e --component', projectPath) - await copyAngularMount(projectPath) - await runCommandInProject('yarn ng run angular:ct --watch false --spec src/app/app.component.cy.ts', projectPath) - }) - - it('should generate component alongside component spec', async () => { - const projectPath = await scaffoldAngularProject(project) - - await runCommandInProject(`yarn add @cypress/schematic@file:${cypressSchematicPackagePath}`, projectPath) - await runCommandInProject('yarn ng add @cypress/schematic --e2e --component', projectPath) - await copyAngularMount(projectPath) - await runCommandInProject('yarn ng generate c foo', projectPath) - await runCommandInProject('yarn ng run angular:ct --watch false --spec src/app/foo/foo.component.cy.ts', projectPath) + describe(project, () => { + const projectPath: string = Fixtures.projectPath(project) + + beforeEach(async () => { + await Fixtures.scaffoldProject(project) + await FixturesScaffold.scaffoldProjectNodeModules({ project }) + await fs.remove(path.join(projectPath, 'cypress.config.ts')) + await fs.remove(path.join(projectPath, 'cypress')) + + await runCommandInProject(`yarn add @cypress/schematic@file:${cypressSchematicPackagePath}`, projectPath) + }, timeout) + + afterEach(() => { + Fixtures.removeProject(project) + }, timeout) + + it('should install ct files with option and no component specs', async () => { + await runCommandInProject('yarn ng add @cypress/schematic --e2e --component', projectPath) + await copyAngularMount(projectPath) + await runCommandInProject('yarn ng run angular:ct --watch false --spec src/app/app.component.cy.ts', projectPath) + }, timeout) + + it('should generate component alongside component spec', async () => { + await runCommandInProject('yarn ng add @cypress/schematic --e2e --component', projectPath) + await copyAngularMount(projectPath) + await runCommandInProject('yarn ng generate c foo', projectPath) + await runCommandInProject('yarn ng run angular:ct --watch false --spec src/app/foo/foo.component.cy.ts', projectPath) + }, timeout) }) } }) diff --git a/npm/cypress-schematic/src/e2e.spec.ts b/npm/cypress-schematic/src/e2e.spec.ts index bd231bc8ea7f..dad285d72ae7 100644 --- a/npm/cypress-schematic/src/e2e.spec.ts +++ b/npm/cypress-schematic/src/e2e.spec.ts @@ -1,3 +1,4 @@ +import { describe, it } from 'vitest' import Fixtures, { ProjectFixtureDir } from '@tooling/system-tests' import * as FixturesScaffold from '@tooling/system-tests/lib/dep-installer' import execa from 'execa' @@ -24,11 +25,9 @@ const runCommandInProject = (command: string, projectPath: string) => { const cypressSchematicPackagePath = path.join(__dirname, '..') -const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-14', 'angular-15'] - -describe('ng add @cypress/schematic / only e2e', function () { - this.timeout(1000 * 60 * 5) +const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-18', 'angular-19'] +describe('ng add @cypress/schematic / only e2e', { timeout: 1000 * 60 * 5 }, function () { for (const project of ANGULAR_PROJECTS) { it('should install e2e files by default', async () => { const projectPath = await scaffoldAngularProject(project) diff --git a/npm/cypress-schematic/src/schematics/ng-add/index.spec.ts b/npm/cypress-schematic/src/schematics/ng-add/index.spec.ts index bef3bf33c0e1..bb7e26473a9f 100644 --- a/npm/cypress-schematic/src/schematics/ng-add/index.spec.ts +++ b/npm/cypress-schematic/src/schematics/ng-add/index.spec.ts @@ -1,8 +1,6 @@ -/// - +import { describe, beforeEach, it, expect } from 'vitest' import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing' import { join } from 'path' -import { expect } from 'chai' import { JsonObject } from '@angular-devkit/core' describe('@cypress/schematic: ng-add', () => { @@ -33,12 +31,12 @@ describe('@cypress/schematic: ng-add', () => { } beforeEach(async () => { - appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions).toPromise() - appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise() + appTree = await schematicRunner.runExternalSchematic('@schematics/angular', 'workspace', workspaceOptions) + appTree = await schematicRunner.runExternalSchematic('@schematics/angular', 'application', appOptions, appTree) }) it('should create cypress files for e2e testing by default', async () => { - await schematicRunner.runSchematicAsync('ng-add', {}, appTree).toPromise().then((tree: UnitTestTree) => { + await schematicRunner.runSchematic('ng-add', {}, appTree).then((tree: UnitTestTree) => { const files = tree.files expect(files).to.contain('/projects/sandbox/cypress/e2e/spec.cy.ts') @@ -51,7 +49,7 @@ describe('@cypress/schematic: ng-add', () => { }) it('should create cypress files for component testing', async () => { - await schematicRunner.runSchematicAsync('ng-add', { 'component': true }, appTree).toPromise().then((tree: UnitTestTree) => { + await schematicRunner.runSchematic('ng-add', { 'component': true }, appTree).then((tree: UnitTestTree) => { const files = tree.files expect(files).to.contain('/projects/sandbox/cypress/support/component.ts') @@ -66,7 +64,7 @@ describe('@cypress/schematic: ng-add', () => { }) it('should add @cypress/schematic to the schemaCollections array', async () => { - const tree = await schematicRunner.runSchematicAsync('ng-add', { 'component': true }, appTree).toPromise() + const tree = await schematicRunner.runSchematic('ng-add', { 'component': true }, appTree) const angularJson = readAngularJson(tree) const cliOptions = angularJson.cli as JsonObject @@ -86,7 +84,7 @@ describe('@cypress/schematic: ng-add', () => { }, })) - const tree = await schematicRunner.runSchematicAsync('ng-add', { 'component': true }, appTree).toPromise() + const tree = await schematicRunner.runSchematic('ng-add', { 'component': true }, appTree) angularJson = readAngularJson(tree) const cliOptions = angularJson.cli as JsonObject diff --git a/npm/cypress-schematic/src/schematics/ng-generate/component/index.spec.ts b/npm/cypress-schematic/src/schematics/ng-generate/component/index.spec.ts index 2c60abc2cf65..24fda3bc67df 100644 --- a/npm/cypress-schematic/src/schematics/ng-generate/component/index.spec.ts +++ b/npm/cypress-schematic/src/schematics/ng-generate/component/index.spec.ts @@ -1,5 +1,5 @@ +import { describe, beforeEach, it, expect } from 'vitest' import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing' -import { expect } from 'chai' import { join } from 'path' describe('ng-generate @cypress/schematic:component', () => { @@ -12,10 +12,10 @@ describe('ng-generate @cypress/schematic:component', () => { const workspaceOptions = { name: 'workspace', newProjectRoot: 'projects', - version: '12.0.0', + version: '18.0.0', } - const appOptions: Parameters[2] = { + const appOptions: Parameters[2] = { name: 'sandbox', inlineTemplate: false, routing: false, @@ -24,12 +24,12 @@ describe('ng-generate @cypress/schematic:component', () => { } beforeEach(async () => { - appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions).toPromise() - appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise() + appTree = await schematicRunner.runExternalSchematic('@schematics/angular', 'workspace', workspaceOptions) + appTree = await schematicRunner.runExternalSchematic('@schematics/angular', 'application', appOptions, appTree) }) it('should create cypress ct alongside the generated component', async () => { - const tree = await schematicRunner.runSchematicAsync('component', { name: 'foo', project: 'sandbox' }, appTree).toPromise() + const tree = await schematicRunner.runSchematic('component', { name: 'foo', project: 'sandbox', skipImport: true }, appTree) expect(tree.files).to.contain('/projects/sandbox/src/app/foo/foo.component.ts') expect(tree.files).to.contain('/projects/sandbox/src/app/foo/foo.component.html') @@ -39,9 +39,9 @@ describe('ng-generate @cypress/schematic:component', () => { }) it('should not generate component which does exist already', async () => { - let tree = await schematicRunner.runSchematicAsync('component', { name: 'foo', project: 'sandbox' }, appTree).toPromise() + let tree = await schematicRunner.runSchematic('component', { name: 'foo', project: 'sandbox', skipImport: true }, appTree) - tree = await schematicRunner.runSchematicAsync('component', { name: 'foo', project: 'sandbox' }, appTree).toPromise() + tree = await schematicRunner.runSchematic('component', { name: 'foo', project: 'sandbox', skipImport: true }, appTree) expect(tree.files.filter((f) => f === '/projects/sandbox/src/app/foo/foo.component.ts').length).to.eq(1) expect(tree.files.filter((f) => f === '/projects/sandbox/src/app/foo/foo.component.html').length).to.eq(1) @@ -50,7 +50,7 @@ describe('ng-generate @cypress/schematic:component', () => { }) it('should generate component given a component containing a directory', async () => { - const tree = await schematicRunner.runSchematicAsync('component', { name: 'foo/bar', project: 'sandbox' }, appTree).toPromise() + const tree = await schematicRunner.runSchematic('component', { name: 'foo/bar', project: 'sandbox', skipImport: true }, appTree) expect(tree.files).to.contain('/projects/sandbox/src/app/foo/bar/bar.component.ts') expect(tree.files).to.contain('/projects/sandbox/src/app/foo/bar/bar.component.html') diff --git a/npm/cypress-schematic/src/schematics/ng-generate/cypress-ct-tests/index.spec.ts b/npm/cypress-schematic/src/schematics/ng-generate/cypress-ct-tests/index.spec.ts index cba6783c3ba8..2ab1b49257a5 100644 --- a/npm/cypress-schematic/src/schematics/ng-generate/cypress-ct-tests/index.spec.ts +++ b/npm/cypress-schematic/src/schematics/ng-generate/cypress-ct-tests/index.spec.ts @@ -1,9 +1,6 @@ -/// - +import { describe, it, beforeEach, expect } from 'vitest' import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing' import { join } from 'path' -import { expect } from 'chai' -import { take } from 'rxjs/operators' describe('ng-generate @cypress/schematic:specs-ct', () => { const schematicRunner = new SchematicTestRunner( @@ -15,10 +12,10 @@ describe('ng-generate @cypress/schematic:specs-ct', () => { const workspaceOptions = { name: 'workspace', newProjectRoot: 'projects', - version: '12.0.0', + version: '18.0.0', } - const appOptions: Parameters[2] = { + const appOptions: Parameters[2] = { name: 'sandbox', inlineTemplate: false, routing: false, @@ -27,11 +24,13 @@ describe('ng-generate @cypress/schematic:specs-ct', () => { } beforeEach(async () => { - appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions).toPromise() - appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise() + appTree = await schematicRunner.runExternalSchematic('@schematics/angular', 'workspace', workspaceOptions) + appTree = await schematicRunner.runExternalSchematic('@schematics/angular', 'application', appOptions, appTree) }) it('should create cypress component tests alongside components', async () => { - return schematicRunner.runSchematicAsync('specs-ct', { project: 'sandbox' }, appTree).pipe(take(1)).subscribe((tree: UnitTestTree) => expect(tree.files).to.contain('/projects/sandbox/app/src/app.component.cy.ts')) + const tree = await schematicRunner.runSchematic('specs-ct', { project: 'sandbox' }, appTree) + + expect(tree.files).to.contain('/projects/sandbox/src/fake-component.component.cy.ts') }) }) diff --git a/npm/cypress-schematic/src/schematics/ng-generate/cypress-test/index.spec.ts b/npm/cypress-schematic/src/schematics/ng-generate/cypress-test/index.spec.ts index 4b70527f5e29..d32826edfb9b 100644 --- a/npm/cypress-schematic/src/schematics/ng-generate/cypress-test/index.spec.ts +++ b/npm/cypress-schematic/src/schematics/ng-generate/cypress-test/index.spec.ts @@ -1,8 +1,6 @@ -/// - +import { describe, it, beforeEach, expect } from 'vitest' import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing' import { join } from 'path' -import { expect } from 'chai' describe('ng-generate @cypress/schematic:spec', () => { const schematicRunner = new SchematicTestRunner( @@ -17,7 +15,7 @@ describe('ng-generate @cypress/schematic:spec', () => { version: '12.0.0', } - const appOptions: Parameters[2] = { + const appOptions: Parameters[2] = { name: 'sandbox', inlineTemplate: false, routing: false, @@ -26,15 +24,19 @@ describe('ng-generate @cypress/schematic:spec', () => { } beforeEach(async () => { - appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions).toPromise() - appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise() + appTree = await schematicRunner.runExternalSchematic('@schematics/angular', 'workspace', workspaceOptions) + appTree = await schematicRunner.runExternalSchematic('@schematics/angular', 'application', appOptions, appTree) }) it('should create cypress e2e spec file by default', async () => { - await schematicRunner.runSchematicAsync('spec', { name: 'foo', project: 'sandbox' }, appTree).toPromise().then((tree: UnitTestTree) => expect(tree.files).to.contain('/projects/sandbox/cypress/e2e/foo.cy.ts')) + const tree = await schematicRunner.runSchematic('spec', { name: 'foo', project: 'sandbox' }, appTree) + + expect(tree.files).to.contain('/projects/sandbox/cypress/e2e/foo.cy.ts') }) it('should create cypress ct spec file when testingType is component', async () => { - await schematicRunner.runSchematicAsync('spec', { name: 'foo', project: 'sandbox', component: true }, appTree).toPromise().then((tree: UnitTestTree) => expect(tree.files).to.contain('/projects/sandbox/src/app/foo.component.cy.ts')) + const tree = await schematicRunner.runSchematic('spec', { name: 'foo', project: 'sandbox', component: true }, appTree) + + expect(tree.files).to.contain('/projects/sandbox/src/app/foo.component.cy.ts') }) }) diff --git a/npm/mount-utils/src/index.ts b/npm/mount-utils/src/index.ts index 4d00f3507161..13976996e8f8 100644 --- a/npm/mount-utils/src/index.ts +++ b/npm/mount-utils/src/index.ts @@ -15,14 +15,6 @@ export const getContainerEl = (): HTMLElement => { throw Error(`No element found that matches selector ${ROOT_SELECTOR}. Please add a root element with data-cy-root attribute to your "component-index.html" file so that Cypress can attach your component to the DOM.`) } -export function checkForRemovedStyleOptions (mountingOptions: Record) { - for (const key of ['cssFile', 'cssFiles', 'style', 'styles', 'stylesheet', 'stylesheets'] as const) { - if (mountingOptions[key]) { - Cypress.utils.throwErrByPath('mount.removed_style_mounting_options', key) - } - } -} - /** * Utility function to register CT side effects and run cleanup code during the "test:before:run" Cypress hook * @param optionalCallback Callback to be called before the next test runs @@ -61,39 +53,3 @@ export function setupHooks (optionalCallback?: Function) { optionalCallback?.() }) } - -/** - * Remove any style or extra link elements from the iframe placeholder - * left from any previous test - * - * Removed as of Cypress 11.0.0 - * @see https://on.cypress.io/migration-11-0-0-component-testing-updates - */ -export function cleanupStyles () { - Cypress.utils.throwErrByPath('mount.cleanup_styles') -} - -/** - * Additional styles to inject into the document. - * A component might need 3rd party libraries from CDN, - * local CSS files and custom styles. - * - * Removed as of Cypress 11.0.0. - * @see https://on.cypress.io/migration-11-0-0-component-testing-updates - */ -export type StyleOptions = unknown - -/** - * Injects custom style text or CSS file or 3rd party style resources - * into the given document. - * - * Removed as of Cypress 11.0.0. - * @see https://on.cypress.io/migration-11-0-0-component-testing-updates - */ -export const injectStylesBeforeElement = ( - options: Partial, - document: Document, - el: HTMLElement | null, -) => { - Cypress.utils.throwErrByPath('mount.inject_styles_before_element') -} diff --git a/npm/puppeteer/package.json b/npm/puppeteer/package.json index 84d5dafd18b7..2c9816035864 100644 --- a/npm/puppeteer/package.json +++ b/npm/puppeteer/package.json @@ -21,7 +21,7 @@ "puppeteer-core": "^21.2.1" }, "devDependencies": { - "@types/node": "^18.17.5", + "@types/node": "^20.16.0", "chai-as-promised": "^7.1.1", "chokidar": "^3.5.3", "express": "4.19.2", diff --git a/npm/react/README.md b/npm/react/README.md index bd5602adfd59..c2bca357d1c9 100644 --- a/npm/react/README.md +++ b/npm/react/README.md @@ -3,6 +3,11 @@ Mount React components in the open source [Cypress.io](https://www.cypress.io/) test runner > **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [React Component Testing Docs](https://docs.cypress.io/guides/component-testing/react/overview) for mounting React components. Installing and importing `mount` from `@cypress/react` should only be done for advanced use-cases. + +## Requirements + +- React 18.0.0+ (Cypress 13 and under supports React 16 and 17) + ## Development Run `yarn build` to compile and sync packages to the `cypress` cli package. diff --git a/npm/react/cypress.config.js b/npm/react/cypress.config.js index 2764f9c4c037..6dbae3753b06 100644 --- a/npm/react/cypress.config.js +++ b/npm/react/cypress.config.js @@ -5,7 +5,6 @@ module.exports = { 'env': { 'reactDevtools': true, }, - 'experimentalFetchPolyfill': true, 'component': { experimentalSingleTabRunMode: true, 'excludeSpecPattern': [ diff --git a/npm/react/cypress/component/advanced/app-action-example/counter.cy.jsx b/npm/react/cypress/component/advanced/app-action-example/counter.cy.jsx index 68caae19c867..bd7296290916 100644 --- a/npm/react/cypress/component/advanced/app-action-example/counter.cy.jsx +++ b/npm/react/cypress/component/advanced/app-action-example/counter.cy.jsx @@ -20,15 +20,13 @@ describe('Counter with access', () => { // the window.counter was set from the Counter's constructor cy.window() .should('have.property', 'counter') - .its('state') - .should('deep.equal', { count: 0 }) + .its('count') + .should('equal', 0) // let's change the state of the component cy.window() .its('counter') - .invoke('setState', { - count: 101, - }) + .invoke('setCount', 101) // the UI should update to reflect the new count cy.contains('count: 101').should('be.visible') diff --git a/npm/react/cypress/component/advanced/app-action-example/counter.jsx b/npm/react/cypress/component/advanced/app-action-example/counter.jsx index b300ff3106e5..1aed4d619089 100644 --- a/npm/react/cypress/component/advanced/app-action-example/counter.jsx +++ b/npm/react/cypress/component/advanced/app-action-example/counter.jsx @@ -1,34 +1,28 @@ -import React from 'react' +import React, { useState } from 'react' -export class Counter extends React.Component { - constructor (props) { - super(props) - this.state = { - count: 0, - } +export function Counter () { + const [count, setCount] = useState(0) - if (window.Cypress) { - // if this component is mounted from inside Cypress Test Runner - // then expose the reference to this instance - // to allow controlling it from tests - console.log( - 'set window.counter to this component in window', - window.location.pathname, - ) + if (window.Cypress) { + // if this component is mounted from inside Cypress Test Runner + // then expose the reference to this instance + // to allow controlling it from tests + console.log( + 'set window.counter to this component in window', + window.location.pathname, + ) - window.counter = this - } else { - console.log('running outside Cypress') + window.counter = { + count, + setCount, } + } else { + console.log('running outside Cypress') } - click = () => { - this.setState({ - count: this.state.count + 1, - }) + function click () { + setCount(count + 1) } - render () { - return

count: {this.state.count}

- } + return

count: {count}

} diff --git a/npm/react/cypress/component/advanced/context/App.jsx b/npm/react/cypress/component/advanced/context/App.jsx index 4620e6c38816..965e0a06815c 100644 --- a/npm/react/cypress/component/advanced/context/App.jsx +++ b/npm/react/cypress/component/advanced/context/App.jsx @@ -2,15 +2,13 @@ import React from 'react' import { ThemeContext } from './context' import { Toolbar } from './Toolbar.jsx' -export default class App extends React.Component { - render () { - // Use a Provider to pass the current theme to the tree below. - // Any component can read it, no matter how deep it is. - // In this example, we're passing "dark" as the current value. - return ( - - - - ) - } +export default function App () { + // Use a Provider to pass the current theme to the tree below. + // Any component can read it, no matter how deep it is. + // In this example, we're passing "dark" as the current value. + return ( + + + + ) } diff --git a/npm/react/cypress/component/advanced/hooks/counter2-with-hooks.jsx b/npm/react/cypress/component/advanced/hooks/counter2-with-hooks.jsx index 8d12775ee3c6..12fee25c14a3 100644 --- a/npm/react/cypress/component/advanced/hooks/counter2-with-hooks.jsx +++ b/npm/react/cypress/component/advanced/hooks/counter2-with-hooks.jsx @@ -6,7 +6,7 @@ export default function Counter2WithHooks () { useEffect(() => { document.title = `You clicked ${count} times` - }) + }, [count]) return (
diff --git a/npm/react/cypress/component/advanced/mocking-axios/1-users.jsx b/npm/react/cypress/component/advanced/mocking-axios/1-users.jsx index 0b7081db11d3..4286947f6674 100644 --- a/npm/react/cypress/component/advanced/mocking-axios/1-users.jsx +++ b/npm/react/cypress/component/advanced/mocking-axios/1-users.jsx @@ -1,36 +1,28 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' // import the entire axios module // later we can use axios.get to make requests import axios from 'axios' -export class Users extends React.Component { - constructor (props) { - super(props) - this.state = { - users: [], - } - } +export function Users () { + const [users, setUsers] = useState([]) - componentDidMount () { - axios - .get('https://jsonplaceholder.cypress.io/users?_limit=3') - .then((response) => { - // JSON responses are automatically parsed. - this.setState({ - users: response.data, - }) - }) - } + const getUsers = async () => { + const response = await axios.get('https://jsonplaceholder.cypress.io/users?_limit=3') - render () { - return ( -
- {this.state.users.map((user) => ( -
  • - {user.id} - {user.name} -
  • - ))} -
    - ) + setUsers(response.data) } + + useEffect(() => { + getUsers() + }, []) + + return ( +
    + {users.map((user) => ( +
  • + {user.id} - {user.name} +
  • + ))} +
    + ) } diff --git a/npm/react/cypress/component/advanced/mocking-axios/3-users-api.cy.jsx b/npm/react/cypress/component/advanced/mocking-axios/2-users-api.cy.jsx similarity index 89% rename from npm/react/cypress/component/advanced/mocking-axios/3-users-api.cy.jsx rename to npm/react/cypress/component/advanced/mocking-axios/2-users-api.cy.jsx index 456c05112e1e..baa74cf22355 100644 --- a/npm/react/cypress/component/advanced/mocking-axios/3-users-api.cy.jsx +++ b/npm/react/cypress/component/advanced/mocking-axios/2-users-api.cy.jsx @@ -1,8 +1,8 @@ /// import React from 'react' import { mount } from '@cypress/react' -import { Users } from './3-users-api.jsx' -import * as axios from './axios-api' +import { Users } from './2-users-api.jsx' +import * as axios from './axios-api.jsx' describe('Mocking wrapped Axios', () => { it('shows real users', () => { diff --git a/npm/react/cypress/component/advanced/mocking-axios/2-users-api.jsx b/npm/react/cypress/component/advanced/mocking-axios/2-users-api.jsx new file mode 100644 index 000000000000..f8d2274fdf7b --- /dev/null +++ b/npm/react/cypress/component/advanced/mocking-axios/2-users-api.jsx @@ -0,0 +1,28 @@ +import React, { useState, useEffect } from 'react' +// import wrapped Axios method +import axiosApi from './axios-api.jsx' + +export function Users () { + const [users, setUsers] = useState([]) + + const getUsers = async () => { + console.log({ axiosApi }) + const response = await axiosApi.get('https://jsonplaceholder.cypress.io/users?_limit=3') + + setUsers(response.data) + } + + useEffect(() => { + getUsers() + }, []) + + return ( +
    + {users.map((user) => ( +
  • + {user.id} - {user.name} +
  • + ))} +
    + ) +} diff --git a/npm/react/cypress/component/advanced/mocking-axios/2-users-named.cy.jsx b/npm/react/cypress/component/advanced/mocking-axios/2-users-named.cy.jsx deleted file mode 100644 index bbb76f2db738..000000000000 --- a/npm/react/cypress/component/advanced/mocking-axios/2-users-named.cy.jsx +++ /dev/null @@ -1,36 +0,0 @@ -/// -import React from 'react' -import { mount } from '@cypress/react' -import { Users } from './2-users-named.jsx' -import axios from 'axios' - -describe('Mocking Axios named import get', () => { - it('shows real users', () => { - mount() - cy.get('li').should('have.length', 3) - }) - - // TODO: Support stubbing ES Modules - it.skip('mocks get', () => { - cy.stub(axios, 'get') - .resolves({ - data: [ - { - id: 101, - name: 'Test User', - }, - ], - }) - .as('get') - - mount() - // only the test user should be shown - cy.get('li').should('have.length', 1) - cy.get('@get').should('have.been.called') - }) - - it('restores the original method', () => { - mount() - cy.get('li').should('have.length', 3) - }) -}) diff --git a/npm/react/cypress/component/advanced/mocking-axios/2-users-named.jsx b/npm/react/cypress/component/advanced/mocking-axios/2-users-named.jsx deleted file mode 100644 index 11be902cda20..000000000000 --- a/npm/react/cypress/component/advanced/mocking-axios/2-users-named.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -// use named import "get" from the module -import { get } from 'axios' - -export class Users extends React.Component { - constructor (props) { - super(props) - this.state = { - users: [], - } - } - - componentDidMount () { - get('https://jsonplaceholder.cypress.io/users?_limit=3').then((response) => { - // JSON responses are automatically parsed. - this.setState({ - users: response.data, - }) - }) - } - - render () { - return ( -
    - {this.state.users.map((user) => ( -
  • - {user.id} - {user.name} -
  • - ))} -
    - ) - } -} diff --git a/npm/react/cypress/component/advanced/mocking-axios/3-users-api.jsx b/npm/react/cypress/component/advanced/mocking-axios/3-users-api.jsx deleted file mode 100644 index 3d7becfc5422..000000000000 --- a/npm/react/cypress/component/advanced/mocking-axios/3-users-api.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -// import wrapped Axios method -import axiosApi from './axios-api' - -export class Users extends React.Component { - constructor (props) { - super(props) - this.state = { - users: [], - } - } - - componentDidMount () { - console.log({ axiosApi }) - axiosApi.get('https://jsonplaceholder.cypress.io/users?_limit=3').then((response) => { - // JSON responses are automatically parsed. - this.setState({ - users: response.data, - }) - }) - } - - render () { - return ( -
    - {this.state.users.map((user) => ( -
  • - {user.id} - {user.name} -
  • - ))} -
    - ) - } -} diff --git a/npm/react/cypress/component/advanced/renderless/mouse-movement.jsx b/npm/react/cypress/component/advanced/renderless/mouse-movement.jsx index 8e838c58b208..7782e8f44d69 100644 --- a/npm/react/cypress/component/advanced/renderless/mouse-movement.jsx +++ b/npm/react/cypress/component/advanced/renderless/mouse-movement.jsx @@ -1,52 +1,44 @@ // https://medium.com/@pierrehedkvist/renderless-components-in-react-8d663746314c -import React from 'react' +// eslint-disable-next-line no-unused-vars +import React, { useState, useEffect } from 'react' import PropTypes from 'prop-types' -export default class MouseMovement extends React.Component { - constructor (props) { - console.log('MouseMovement constructor') - super(props) - this.state = { - timer: undefined, - } - - this.timeout = this.timeout.bind(this) - this.onMouseMove = this.onMouseMove.bind(this) - } +export default function MouseMovement ({ onMoved }) { + const [timer, setTimer] = useState(undefined) - componentWillMount () { - console.log('MouseMovement componentWillMount') - document.addEventListener('mousemove', this.onMouseMove) - const timer = setTimeout(this.timeout, 4000) + function onMouseMove () { + console.log('MouseMovement onMouseMove') + clearTimeout(timer) + const timerNew = setTimeout(timeout, 4000) - this.setState({ timer }) + setTimer(timerNew) + onMoved(true) } - componentWillUnmount () { - console.log('MouseMovement componentWillUnmount') - document.removeEventListener('mousemove', this.onMouseMove) - clearTimeout(this.state.timer) - this.setState({ timer: undefined }) + function timeout () { + console.log('timeout') + clearTimeout(timer) + onMoved(false) } - onMouseMove () { - console.log('MouseMovement onMouseMove') - clearTimeout(this.state.timer) - const timer = setTimeout(this.timeout, 4000) + useEffect(() => { + // Anything in here is fired on component mount. + console.log('MouseMovement componentWillMount') + document.addEventListener('mousemove', onMouseMove) + const timerNew = setTimeout(timeout, 4000) - this.setState({ timer }) - this.props.onMoved(true) - } + setTimer(timerNew) - timeout () { - console.log('timeout') - clearTimeout(this.state.timer) - this.props.onMoved(false) - } + return () => { + // Anything in here is fired on component unmount. + console.log('MouseMovement componentWillUnmount') + document.removeEventListener('mousemove', onMouseMove) + clearTimeout(timer) + setTimer(undefined) + } + }, []) - render () { - return null - } + return null } MouseMovement.propTypes = { diff --git a/npm/react/cypress/component/advanced/renderless/mouse.cy.jsx b/npm/react/cypress/component/advanced/renderless/mouse.cy.jsx index 670d3dc92214..4bd6984dac03 100644 --- a/npm/react/cypress/component/advanced/renderless/mouse.cy.jsx +++ b/npm/react/cypress/component/advanced/renderless/mouse.cy.jsx @@ -5,14 +5,6 @@ import MouseMovement from './mouse-movement' describe('Renderless component', () => { it('works', () => { - // let's also spy on "console.log" calls - // to make sure the entire sequence of calls happens - cy.window() - .its('console') - .then((console) => { - cy.spy(console, 'log').as('log') - }) - const onMoved = cy.stub() mount() @@ -23,27 +15,7 @@ describe('Renderless component', () => { expect(onMoved).to.have.been.calledWith(true) }) - // mount something else to trigger unmount + // mount something else to trigger unmount and stop log flow mount(
    Test Component
    ) - - cy.get('@log') - .its('callCount') - .should('equal', 4) - - cy.get('@log') - .invoke('getCalls') - .then((calls) => { - return calls.map((call) => { - console.log('one', call.args[0]) - - return call.args[0] - }) - }) - .should('deep.equal', [ - 'MouseMovement constructor', - 'MouseMovement componentWillMount', - 'MouseMovement onMouseMove', - 'MouseMovement componentWillUnmount', - ]) }) }) diff --git a/npm/react/cypress/component/advanced/set-timeout-example/App.jsx b/npm/react/cypress/component/advanced/set-timeout-example/App.jsx index 6c4aa515a9a0..c0cd7c76af93 100644 --- a/npm/react/cypress/component/advanced/set-timeout-example/App.jsx +++ b/npm/react/cypress/component/advanced/set-timeout-example/App.jsx @@ -1,35 +1,31 @@ -import React, { Component } from 'react' +import React, { useState, useEffect } from 'react' import './App.css' import logo from './logo.svg' import LoadingIndicator from './LoadingIndicator' -class App extends Component { - state = { - isLoading: true, - } +function App () { + const [isLoading, setIsLoading] = useState(true) - componentDidMount () { - this._timer = setTimeout(() => this.setState({ isLoading: false }), 2000) - } + useEffect(() => { + const _timer = setTimeout(() => setIsLoading(false), 2000) - componentWillUnmount () { - clearTimeout(this._timer) - } + return () => { + clearTimeout(_timer) + } + }, []) - render () { - return ( -
    -
    - logo -

    Welcome to React

    -
    -
    isLoading: {String(this.state.isLoading)}
    - -
    ahoy!
    -
    -
    - ) - } + return ( +
    +
    + logo +

    Welcome to React

    +
    +
    isLoading: {String(isLoading)}
    + +
    ahoy!
    +
    +
    + ) } export default App diff --git a/npm/react/cypress/component/advanced/set-timeout-example/LoadingIndicator.jsx b/npm/react/cypress/component/advanced/set-timeout-example/LoadingIndicator.jsx index 0c6cdfec36dc..f4a57d7431c6 100644 --- a/npm/react/cypress/component/advanced/set-timeout-example/LoadingIndicator.jsx +++ b/npm/react/cypress/component/advanced/set-timeout-example/LoadingIndicator.jsx @@ -1,37 +1,26 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' +import React, { useState, useEffect } from 'react' -export default class LoadingIndicator extends Component { - static propTypes = { - isLoading: PropTypes.bool.isRequired, - } - - state = { - isPastDelay: false, - } +export default function LoadingIndicator ({ isLoading, children }) { + const [isPastDelay, setIsPastDelay] = useState(false) - componentDidMount () { - console.log('component did mount') - this._delayTimer = setTimeout(() => { + useEffect(() => { + const _delayTimer = setTimeout(() => { console.log('2000ms passed') - this.setState({ isPastDelay: true }) + setIsPastDelay(true) }, 2000) - } - componentWillUnmount () { - console.log('componentWillUnmount') - clearTimeout(this._delayTimer) - } - - render () { - if (this.props.isLoading) { - if (!this.state.isPastDelay) { - return null - } + return () => { + clearTimeout(_delayTimer) + } + }, []) - return
    loading...
    + if (isLoading) { + if (!isPastDelay) { + return null } - return this.props.children + return
    loading...
    } + + return children } diff --git a/npm/react/cypress/component/advanced/timers/card-without-effect.jsx b/npm/react/cypress/component/advanced/timers/card-without-effect.jsx index e85bc2ef301d..e1b40c692353 100644 --- a/npm/react/cypress/component/advanced/timers/card-without-effect.jsx +++ b/npm/react/cypress/component/advanced/timers/card-without-effect.jsx @@ -1,5 +1,6 @@ import React, { Component } from 'react' +// Class components will be removed in a future release of React 18+. Until then, this example will serve as a class component example export default class Card extends Component { componentDidMount () { this._timeoutID = setTimeout(() => { diff --git a/npm/react/cypress/component/advanced/tutorial/shopping-list.jsx b/npm/react/cypress/component/advanced/tutorial/shopping-list.jsx index b2ea82ee7b94..7dec846617b5 100644 --- a/npm/react/cypress/component/advanced/tutorial/shopping-list.jsx +++ b/npm/react/cypress/component/advanced/tutorial/shopping-list.jsx @@ -1,16 +1,14 @@ import React from 'react' -export default class ShoppingList extends React.Component { - render () { - return ( -
    -

    Shopping List for {this.props.name}

    -
      -
    • Instagram
    • -
    • WhatsApp
    • -
    • Oculus
    • -
    -
    - ) - } +export default function ShoppingList ({ name }) { + return ( +
    +

    Shopping List for {name}

    +
      +
    • Instagram
    • +
    • WhatsApp
    • +
    • Oculus
    • +
    +
    + ) } diff --git a/npm/react/cypress/component/advanced/tutorial/square.cy.jsx b/npm/react/cypress/component/advanced/tutorial/square.cy.jsx index 33e70df52aaf..57aaa5a3d17c 100644 --- a/npm/react/cypress/component/advanced/tutorial/square.cy.jsx +++ b/npm/react/cypress/component/advanced/tutorial/square.cy.jsx @@ -1,27 +1,17 @@ /// -import React from 'react' +import React, { useState } from 'react' import { mount } from '@cypress/react' import './tic-tac-toe.css' // let's put React component right in the spec file -class Square extends React.Component { - constructor (props) { - super(props) - this.state = { - value: null, - } - } +export default function Square ({ value: valueAsProp }) { + const [valueAsState, setValueAsState] = useState(null) - render () { - return ( - - ) - } + return ( + + ) } describe('Square', () => { diff --git a/npm/react/cypress/component/advanced/tutorial/tic-tac-toe.cy.jsx b/npm/react/cypress/component/advanced/tutorial/tic-tac-toe.cy.jsx index c4b93f89803c..57bfea3cc867 100644 --- a/npm/react/cypress/component/advanced/tutorial/tic-tac-toe.cy.jsx +++ b/npm/react/cypress/component/advanced/tutorial/tic-tac-toe.cy.jsx @@ -3,7 +3,7 @@ /// import React from 'react' import { mount } from '@cypress/react' -import { Game } from './tic-tac-toe.jsx' +import Game from './tic-tac-toe.jsx' import './tic-tac-toe.css' describe('Tic Tac Toe', () => { diff --git a/npm/react/cypress/component/advanced/tutorial/tic-tac-toe.jsx b/npm/react/cypress/component/advanced/tutorial/tic-tac-toe.jsx index 35c4a704c017..b7b0f2f76013 100644 --- a/npm/react/cypress/component/advanced/tutorial/tic-tac-toe.jsx +++ b/npm/react/cypress/component/advanced/tutorial/tic-tac-toe.jsx @@ -1,115 +1,125 @@ -// entire game from the tutorial inside the spec for simplicity -// the code taken from https://codepen.io/gaearon/pen/LyyXgK -import React from 'react' +import React, { useState } from 'react' -export function calculateWinner (squares) { - const lines = [ - [0, 1, 2], - [3, 4, 5], - [6, 7, 8], - [0, 3, 6], - [1, 4, 7], - [2, 5, 8], - [0, 4, 8], - [2, 4, 6], - ] +function Square ({ value, onSquareClick }) { + return ( + + ) +} - for (let i = 0; i < lines.length; i++) { - const [a, b, c] = lines[i] +export function Board ({ xIsNext, squares, onPlay }) { + function handleClick (i) { + if (calculateWinner(squares) || squares[i]) { + return + } - if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { - return squares[a] + const nextSquares = squares.slice() + + if (xIsNext) { + nextSquares[i] = 'X' + } else { + nextSquares[i] = 'O' } + + onPlay(nextSquares) } - return null -} + const winner = calculateWinner(squares) + let status + + if (winner) { + status = `Winner: ${ winner}` + } else { + status = `Next player: ${ xIsNext ? 'X' : 'O'}` + } -function Square (props) { return ( - + <> +
    {status}
    +
    + handleClick(0)} /> + handleClick(1)} /> + handleClick(2)} /> +
    +
    + handleClick(3)} /> + handleClick(4)} /> + handleClick(5)} /> +
    +
    + handleClick(6)} /> + handleClick(7)} /> + handleClick(8)} /> +
    + ) } -export class Board extends React.Component { - constructor (props) { - super(props) - this.state = { - squares: Array(9).fill(null), - xIsNext: true, - } - } +export default function Game () { + const [history, setHistory] = useState([Array(9).fill(null)]) + const [currentMove, setCurrentMove] = useState(0) + const xIsNext = currentMove % 2 === 0 + const currentSquares = history[currentMove] - handleClick (i) { - const squares = this.state.squares.slice() + function handlePlay (nextSquares) { + const nextHistory = [...history.slice(0, currentMove + 1), nextSquares] - if (calculateWinner(squares) || squares[i]) { - return - } - - squares[i] = this.state.xIsNext ? 'X' : 'O' - this.setState({ - squares, - xIsNext: !this.state.xIsNext, - }) + setHistory(nextHistory) + setCurrentMove(nextHistory.length - 1) } - renderSquare (i) { - return ( - this.handleClick(i)} - /> - ) + function jumpTo (nextMove) { + setCurrentMove(nextMove) } - render () { - const winner = calculateWinner(this.state.squares) - let status + const moves = history.map((squares, move) => { + let description - if (winner) { - status = `Winner: ${winner}` + if (move > 0) { + description = `Go to move #${ move}` } else { - status = `Next player: ${this.state.xIsNext ? 'X' : 'O'}` + description = 'Go to game start' } return ( -
    -
    {status}
    -
    - {this.renderSquare(0)} - {this.renderSquare(1)} - {this.renderSquare(2)} -
    -
    - {this.renderSquare(3)} - {this.renderSquare(4)} - {this.renderSquare(5)} -
    -
    - {this.renderSquare(6)} - {this.renderSquare(7)} - {this.renderSquare(8)} -
    -
    +
  • + +
  • ) - } -} + }) -export class Game extends React.Component { - render () { - return ( -
    -
    - -
    -
    -
    {/* status */}
    -
      {/* TODO */}
    -
    + return ( +
    +
    +
    - ) +
    +
      {moves}
    +
    +
    + ) +} + +export function calculateWinner (squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ] + + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i] + + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a] + } } + + return null } diff --git a/npm/react/cypress/component/basic/css/Button.jsx b/npm/react/cypress/component/basic/css/Button.jsx index f2801f5dc218..69564bb12127 100644 --- a/npm/react/cypress/component/basic/css/Button.jsx +++ b/npm/react/cypress/component/basic/css/Button.jsx @@ -1,22 +1,20 @@ import React from 'react' import './Button.css' -export class Button extends React.Component { - handleClick () { - this.props.clickHandler(this.props.name) - } - - render () { - const className = [ - 'component-button', - this.props.orange ? 'orange' : '', - this.props.wide ? 'wide' : '', - ] +export function Button ({ name, orange, wide, clickHandler }) { + const className = [ + 'component-button', + orange ? 'orange' : '', + wide ? 'wide' : '', + ] - return ( -
    - -
    - ) + function handleClick () { + clickHandler(name) } + + return ( +
    + +
    + ) } diff --git a/npm/react/cypress/component/basic/enzyme/README.md b/npm/react/cypress/component/basic/enzyme/README.md deleted file mode 100644 index 947f0b442ed8..000000000000 --- a/npm/react/cypress/component/basic/enzyme/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# Enzyme examples - -This folder shows several examples from [Enzyme docs](https://enzymejs.github.io/enzyme/). - -In general if you are migrating from Enzyme to `@cypress/react`: - -- there is no shallow mounting, only the full mounting. Thus `@cypress/react` has `mount` which is similar to the Enzyme's `render`. It renders the full HTML and CSS output of your component. -- you can mock [children components](https://github.com/bahmutov/cypress-react-unit-test/tree/main/cypress/component/advanced/mocking-component) if you want to avoid running "expensive" components during tests -- the test is running as a "mini" web application. Thus if you want to set a context around component, then set the [context around the component](https://github.com/bahmutov/cypress-react-unit-test/tree/main/cypress/component/advanced/context) - -## setState - -If you want to change the component's internal state, use the component reference. You can get it by using the special property `ref` when mounting. - -```js -// get the component reference using "ref" prop -// and place it into the object for Cypress to "wait" for it -let c = {} -mount( (c.instance = i)} />) -cy.wrap(c) - .its('instance') - .invoke('setState', { count: 10 }) -``` - -See [state-spec.js](state-spec.js) file. - -## setProps - -There is no direct implementation of `setProps`. If you want to see how the component behaves with different props: - -```js -it('mounts component with new props', () => { - mount() - cy.contains('initial').should('be.visible') - - mount() - cy.contains('second').should('be.visible') -}) -``` - -If you want to reuse properties, you can even clone the component - -```js -it('mounts cloned component', () => { - const cmp = - mount(cmp) - cy.contains('initial').should('be.visible') - - const cloned = Cypress._.cloneDeep(cmp) - // change a property, leaving the rest unchanged - cloned.props.foo = 'second' - mount(cloned) - cy.contains('.foo', 'second').should('be.visible') -}) -``` - -See [props-spec.js](props-spec.js) file. - -## context - -Enzyme's `mount` method allows passing the [React context](https://reactjs.org/docs/context.html) as the second argument to the JSX component like `SimpleComponent` below. - -```js -function SimpleComponent(props, context) { - const { name } = context - return
    {name || 'not set'}
    -} -``` - -Since the above syntax is [deprecated](https://reactjs.org/docs/legacy-context.html), `@cypress/react` does not support it. Instead use `createContext` and `Context.Provider` to surround the mounted component, just like you would do in a regular application code. - -```js -mount( - - - , -) -``` - -Instead of setting a new context, mount the same component but surround it with a different context provider - -```js -const cmp = -mount( - - {cmp} - , -) - -// same component, different provider -mount( - - {cmp} - , -) -``` - -See [context-spec.js](context-spec.js) for more examples. diff --git a/npm/react/cypress/component/basic/enzyme/context.cy.jsx b/npm/react/cypress/component/basic/enzyme/context.cy.jsx deleted file mode 100644 index 87e660cab7af..000000000000 --- a/npm/react/cypress/component/basic/enzyme/context.cy.jsx +++ /dev/null @@ -1,53 +0,0 @@ -/// -import React from 'react' -import { mount } from '@cypress/react' -import { SimpleContext } from './simple-context' -import { SimpleComponent } from './simple-component.jsx' - -// testing components that use Context React API -// https://reactjs.org/docs/context.html -describe('Enzyme', () => { - context('setContext', () => { - it('does not provide the context', () => { - mount() - cy.contains('context not set').should('be.visible') - }) - - it('provides the context', () => { - // surround the component with the real provider but - // set the value prop to whatever the test requires - mount( - - - , - ) - - cy.contains('test context').should('be.visible') - }) - - it('mounts new context', () => { - // instead of setting the context from the test - // just mount the component again with a different provider around it - const cmp = - - mount( - - {cmp} - , - ) - - cy.contains('first context').should('be.visible') - cy.contains('.id', '0x123').should('be.visible') - - // same component, different provider - mount( - - {cmp} - , - ) - - cy.contains('second context').should('be.visible') - cy.contains('.id', '0x123').should('be.visible') - }) - }) -}) diff --git a/npm/react/cypress/component/basic/enzyme/props.cy.jsx b/npm/react/cypress/component/basic/enzyme/props.cy.jsx deleted file mode 100644 index 0ccb58844c06..000000000000 --- a/npm/react/cypress/component/basic/enzyme/props.cy.jsx +++ /dev/null @@ -1,82 +0,0 @@ -/// -import React from 'react' -import { mount } from '@cypress/react' - -class Foo extends React.Component { - constructor (props) { - super(props) - - this.state = { - count: 0, - } - } - - componentDidMount () { - console.log('componentDidMount called') - } - - componentDidUpdate () { - console.log('componentDidUpdate called') - } - - render () { - const { id, foo } = this.props - - return ( -
    - {foo} count {this.state.count} -
    - ) - } -} - -describe('Enzyme', () => { - // example test copied from - // https://github.com/enzymejs/enzyme/blob/master/packages/enzyme-test-suite/test/shared/methods/setProps.jsx - - context('setProps', () => { - it('gets props from the component', () => { - mount().as('Foo') - cy.contains('initial').should('be.visible') - - cy.get('@Foo') - .its('component') - .its('props') - .then((props) => { - console.log('current props', props) - expect(props).to.deep.equal({ - id: 'foo', - foo: 'initial', - }) - - // you can get current props of the component - // but not change them - they are read-only - expect(() => { - props.foo = 'change 1' - }).to.throw() - }) - }) - - it('mounts component with new props', () => { - mount() - cy.contains('initial').should('be.visible') - - mount() - cy.contains('second').should('be.visible') - }) - - it('mounts cloned component', () => { - const cmp = - - mount(cmp) - cy.contains('initial').should('be.visible') - - const cloned = Cypress._.cloneDeep(cmp) - - // change a property, leaving the rest unchanged - cloned.props.foo = 'second' - mount(cloned) - cy.contains('.foo', 'second').should('be.visible') - }) - }) -}) diff --git a/npm/react/cypress/component/basic/enzyme/simple-component.jsx b/npm/react/cypress/component/basic/enzyme/simple-component.jsx deleted file mode 100644 index 57eacb77b2f8..000000000000 --- a/npm/react/cypress/component/basic/enzyme/simple-component.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' -import { SimpleContext } from './simple-context' - -export class SimpleComponent extends React.Component { - constructor (props) { - super(props) - this.state = { - id: props.id || 'unknown id', - } - } - - render () { - console.log('context %o', this.context) - - return ( - <> -
    {this.context.name || 'context not set'}
    -
    {this.state.id}
    - - ) - } -} - -SimpleComponent.contextType = SimpleContext diff --git a/npm/react/cypress/component/basic/enzyme/simple-context.jsx b/npm/react/cypress/component/basic/enzyme/simple-context.jsx deleted file mode 100644 index 80fd038f2e82..000000000000 --- a/npm/react/cypress/component/basic/enzyme/simple-context.jsx +++ /dev/null @@ -1,4 +0,0 @@ -// https://reactjs.org/docs/context.html -import { createContext } from 'react' - -export const SimpleContext = createContext({ name: '' }) diff --git a/npm/react/cypress/component/basic/enzyme/state.cy.jsx b/npm/react/cypress/component/basic/enzyme/state.cy.jsx deleted file mode 100644 index 2f3db3157097..000000000000 --- a/npm/react/cypress/component/basic/enzyme/state.cy.jsx +++ /dev/null @@ -1,60 +0,0 @@ -/// -import React from 'react' -import { mount } from '@cypress/react' - -class Foo extends React.Component { - constructor (props) { - super(props) - - this.state = { - count: 0, - } - } - - componentDidMount () { - console.log('componentDidMount called') - } - - componentDidUpdate () { - console.log('componentDidUpdate called') - } - - render () { - const { id, foo } = this.props - - return ( -
    - {foo} count {this.state.count} -
    - ) - } -} - -describe('Enzyme', () => { - context('setState', () => { - it('sets component state', () => { - // get the component reference using "ref" prop - // and place it into the object for Cypress to "wait" for it - let c = {} - - mount( (c.instance = i)} />) - cy.contains('initial').should('be.visible') - - cy.log('**check state**') - cy.wrap(c) - .its('instance.state') - .should('deep.equal', { count: 0 }) - - cy.log('**setState**') - cy.wrap(c) - .its('instance') - .invoke('setState', { count: 10 }) - - cy.wrap(c) - .its('instance.state') - .should('deep.equal', { count: 10 }) - - cy.contains('initial count 10') - }) - }) -}) diff --git a/npm/react/cypress/component/basic/hello-world.cy.jsx b/npm/react/cypress/component/basic/hello-world.cy.jsx index 7593fd71e05e..6bc8893a70eb 100644 --- a/npm/react/cypress/component/basic/hello-world.cy.jsx +++ b/npm/react/cypress/component/basic/hello-world.cy.jsx @@ -9,10 +9,4 @@ describe('HelloWorld component', () => { mount() cy.contains('Hello World!') }) - - it('errors if passing alias', () => { - expect(() => mount(, { alias: 'foo' })).to.throw( - `passing \`alias\` to mounting options is no longer supported. Use mount(...).as('foo') instead.`, - ) - }) }) diff --git a/npm/react/cypress/component/basic/network/1-users.jsx b/npm/react/cypress/component/basic/network/1-users.jsx index 6f879c6f6337..feb0aafacfdb 100644 --- a/npm/react/cypress/component/basic/network/1-users.jsx +++ b/npm/react/cypress/component/basic/network/1-users.jsx @@ -1,34 +1,26 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' import axios from 'axios' -export class Users extends React.Component { - constructor (props) { - super(props) - this.state = { - users: [], - } - } +export function Users () { + const [users, setUsers] = useState([]) - componentDidMount () { - axios - .get('https://jsonplaceholder.cypress.io/users?_limit=3') - .then((response) => { - // JSON responses are automatically parsed. - this.setState({ - users: response.data, - }) - }) - } + const getUsers = async () => { + const response = await axios.get('https://jsonplaceholder.cypress.io/users?_limit=3') - render () { - return ( -
    - {this.state.users.map((user) => ( -
  • - {user.id} - {user.name} -
  • - ))} -
    - ) + setUsers(response.data) } + + useEffect(() => { + getUsers() + }, []) + + return ( +
    + {users.map((user) => ( +
  • + {user.id} - {user.name} +
  • + ))} +
    + ) } diff --git a/npm/react/cypress/component/basic/network/2-users-fetch.jsx b/npm/react/cypress/component/basic/network/2-users-fetch.jsx index f63f26c53254..b5d302291010 100644 --- a/npm/react/cypress/component/basic/network/2-users-fetch.jsx +++ b/npm/react/cypress/component/basic/network/2-users-fetch.jsx @@ -1,34 +1,26 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' -export class Users extends React.Component { - constructor (props) { - super(props) - this.state = { - users: [], - } - } +export function Users () { + const [users, setUsers] = useState([]) - componentDidMount () { - fetch('https://jsonplaceholder.cypress.io/users?_limit=3') - .then((response) => { - return response.json() - }) - .then((list) => { - this.setState({ - users: list, - }) - }) - } + const getUsers = async () => { + const response = await fetch('https://jsonplaceholder.cypress.io/users?_limit=3') + const list = await response.json() - render () { - return ( -
    - {this.state.users.map((user) => ( -
  • - {user.id} - {user.name} -
  • - ))} -
    - ) + setUsers(list) } + + useEffect(() => { + getUsers() + }, []) + + return ( +
    + {users.map((user) => ( +
  • + {user.id} - {user.name} +
  • + ))} +
    + ) } diff --git a/npm/react/cypress/component/basic/network/README.md b/npm/react/cypress/component/basic/network/README.md index 2d1c2eea9bd8..a5d90d66d20a 100644 --- a/npm/react/cypress/component/basic/network/README.md +++ b/npm/react/cypress/component/basic/network/README.md @@ -1,4 +1,3 @@ # Mocking network - [1-users-spec.js](1-users-spec.js) tests [1-users.jsx](1-users.jsx) that uses Axios to GET a list of users. Axios uses XMLHttpRequest to receive the data -- [2-users-fetch-spec.js](2-users-fetch-spec.js) tests [2-users-fetch.jsx](2-users-fetch.jsx) that uses `fetch` directly, assuming `"experimentalFetchPolyfill": true` in `cypress.json`, read [Experimental Fetch Polyfill](https://www.cypress.io/blog/2020/06/29/experimental-fetch-polyfill/) diff --git a/npm/react/cypress/component/basic/react-tutorial/game.cy.jsx b/npm/react/cypress/component/basic/react-tutorial/game.cy.jsx index c365c60638c0..263c638db5c0 100644 --- a/npm/react/cypress/component/basic/react-tutorial/game.cy.jsx +++ b/npm/react/cypress/component/basic/react-tutorial/game.cy.jsx @@ -5,11 +5,11 @@ import { mount } from '@cypress/react' import './tic-tac-toe.css' // for now need a constructor, otherwise getting "Weak map" key -const BoardWrap = ({ squares, onClick }) => { +const BoardWrap = ({ squares, onPlay }) => { return (
    - +
    ) @@ -21,16 +21,16 @@ beforeEach(() => { it('renders empty Board', () => { const squares = Array(9).fill(null) - const onClick = cy.stub() + const onPlay = cy.stub() - mount() + mount() cy.get('.board-row') .eq(0) .find('.square') .eq(0) .click() .then(() => { - expect(onClick).to.have.been.calledWith(0) + expect(onPlay).to.have.been.called }) }) @@ -55,7 +55,7 @@ it('renders Board with a few squares filled', () => { it('plays the game', () => { mount() - cy.contains('.game-info', 'Next player: X').should('be.visible') + cy.contains('.game-board > .status', 'Next player: X').should('be.visible') cy.get('.board-row') .eq(0) .find('.square') @@ -87,7 +87,7 @@ it('plays the game', () => { .eq(2) .click() - cy.contains('.game-info', 'Winner: X').should('be.visible') + cy.contains('.game-board > .status', 'Winner: X').should('be.visible') // history of moves cy.get('ol li') .should('have.length', 6) diff --git a/npm/react/cypress/component/basic/react-tutorial/game.jsx b/npm/react/cypress/component/basic/react-tutorial/game.jsx index 65f9f6c12b74..b7b0f2f76013 100644 --- a/npm/react/cypress/component/basic/react-tutorial/game.jsx +++ b/npm/react/cypress/component/basic/react-tutorial/game.jsx @@ -1,129 +1,105 @@ -// from https://codepen.io/gaearon/pen/gWWZgR -import React from 'react' +import React, { useState } from 'react' -function Square (props) { +function Square ({ value, onSquareClick }) { return ( - ) } -export class Board extends React.Component { - renderSquare (i) { - return ( - this.props.onClick(i)} - /> - ) - } - - render () { - return ( -
    -
    - {this.renderSquare(0)} - {this.renderSquare(1)} - {this.renderSquare(2)} -
    -
    - {this.renderSquare(3)} - {this.renderSquare(4)} - {this.renderSquare(5)} -
    -
    - {this.renderSquare(6)} - {this.renderSquare(7)} - {this.renderSquare(8)} -
    -
    - ) - } -} - -export default class Game extends React.Component { - constructor (props) { - super(props) - this.state = { - history: [ - { - squares: Array(9).fill(null), - }, - ], - stepNumber: 0, - xIsNext: true, +export function Board ({ xIsNext, squares, onPlay }) { + function handleClick (i) { + if (calculateWinner(squares) || squares[i]) { + return } - } - handleClick (i) { - const history = this.state.history.slice(0, this.state.stepNumber + 1) - const current = history[history.length - 1] - const squares = current.squares.slice() + const nextSquares = squares.slice() - if (calculateWinner(squares) || squares[i]) { - return + if (xIsNext) { + nextSquares[i] = 'X' + } else { + nextSquares[i] = 'O' } - squares[i] = this.state.xIsNext ? 'X' : 'O' - this.setState({ - history: history.concat([ - { - squares, - }, - ]), - stepNumber: history.length, - xIsNext: !this.state.xIsNext, - }) + onPlay(nextSquares) } - jumpTo (step) { - this.setState({ - stepNumber: step, - xIsNext: step % 2 === 0, - }) + const winner = calculateWinner(squares) + let status + + if (winner) { + status = `Winner: ${ winner}` + } else { + status = `Next player: ${ xIsNext ? 'X' : 'O'}` } - render () { - const history = this.state.history - const current = history[this.state.stepNumber] - const winner = calculateWinner(current.squares) + return ( + <> +
    {status}
    +
    + handleClick(0)} /> + handleClick(1)} /> + handleClick(2)} /> +
    +
    + handleClick(3)} /> + handleClick(4)} /> + handleClick(5)} /> +
    +
    + handleClick(6)} /> + handleClick(7)} /> + handleClick(8)} /> +
    + + ) +} + +export default function Game () { + const [history, setHistory] = useState([Array(9).fill(null)]) + const [currentMove, setCurrentMove] = useState(0) + const xIsNext = currentMove % 2 === 0 + const currentSquares = history[currentMove] + + function handlePlay (nextSquares) { + const nextHistory = [...history.slice(0, currentMove + 1), nextSquares] - const moves = history.map((step, move) => { - const desc = move ? `Go to move #${move}` : 'Go to game start' + setHistory(nextHistory) + setCurrentMove(nextHistory.length - 1) + } - return ( -
  • - -
  • - ) - }) + function jumpTo (nextMove) { + setCurrentMove(nextMove) + } - let status + const moves = history.map((squares, move) => { + let description - if (winner) { - status = `Winner: ${winner}` + if (move > 0) { + description = `Go to move #${ move}` } else { - status = `Next player: ${this.state.xIsNext ? 'X' : 'O'}` + description = 'Go to game start' } return ( -
    -
    - this.handleClick(i)} /> -
    -
    -
    {status}
    -
      {moves}
    -
    -
    +
  • + +
  • ) - } -} + }) -// ======================================== - -// ReactDOM.render(, document.getElementById('root')) + return ( +
    +
    + +
    +
    +
      {moves}
    +
    +
    + ) +} export function calculateWinner (squares) { const lines = [ diff --git a/npm/react/cypress/component/basic/react-tutorial/shopping-list.jsx b/npm/react/cypress/component/basic/react-tutorial/shopping-list.jsx index 5d922a6b122b..8b117f38cfeb 100644 --- a/npm/react/cypress/component/basic/react-tutorial/shopping-list.jsx +++ b/npm/react/cypress/component/basic/react-tutorial/shopping-list.jsx @@ -1,18 +1,16 @@ import React from 'react' -export default class ShoppingList extends React.Component { - render () { - return ( -
    -

    Shopping List for {this.props.name}

    -
      -
    • Instagram
    • -
    • WhatsApp
    • -
    • Oculus
    • -
    -
    - ) - } +export default function ShoppingList ({ name }) { + return ( +
    +

    Shopping List for {name}

    +
      +
    • Instagram
    • +
    • WhatsApp
    • +
    • Oculus
    • +
    +
    + ) } // Example usage: diff --git a/npm/react/cypress/component/basic/react-tutorial/square1.jsx b/npm/react/cypress/component/basic/react-tutorial/square1.jsx index c2ca98ddd64b..60bde2cb49a6 100644 --- a/npm/react/cypress/component/basic/react-tutorial/square1.jsx +++ b/npm/react/cypress/component/basic/react-tutorial/square1.jsx @@ -1,11 +1,9 @@ import React from 'react' -export default class Square extends React.Component { - render () { - return ( - - ) - } +export default function Square ({ value }) { + return ( + + ) } diff --git a/npm/react/cypress/component/basic/react-tutorial/square2.jsx b/npm/react/cypress/component/basic/react-tutorial/square2.jsx index 564fd505c3de..9869d3ef332b 100644 --- a/npm/react/cypress/component/basic/react-tutorial/square2.jsx +++ b/npm/react/cypress/component/basic/react-tutorial/square2.jsx @@ -1,18 +1,9 @@ import React from 'react' -export default class Square extends React.Component { - constructor (props) { - super(props) - this.state = { - value: null, - } - } - - render () { - return ( - - ) - } +export default function Square ({ value }) { + return ( + + ) } diff --git a/npm/react/cypress/component/basic/react-tutorial/square3.jsx b/npm/react/cypress/component/basic/react-tutorial/square3.jsx index 35bc02beee2f..1c5122962847 100644 --- a/npm/react/cypress/component/basic/react-tutorial/square3.jsx +++ b/npm/react/cypress/component/basic/react-tutorial/square3.jsx @@ -1,18 +1,11 @@ -import React from 'react' +import React, { useState } from 'react' -export default class Square extends React.Component { - constructor (props) { - super(props) - this.state = { - value: null, - } - } +export default function Square () { + const [value, setValue] = useState(null) - render () { - return ( - - ) - } + return ( + + ) } diff --git a/npm/react/cypress/component/basic/react-tutorial/square4.jsx b/npm/react/cypress/component/basic/react-tutorial/square4.jsx index 507259650fa1..97ee0aaed834 100644 --- a/npm/react/cypress/component/basic/react-tutorial/square4.jsx +++ b/npm/react/cypress/component/basic/react-tutorial/square4.jsx @@ -1,9 +1,9 @@ import React from 'react' -export default function Square (props) { +export default function Square ({ value, onClick }) { return ( - ) } diff --git a/npm/react/cypress/component/basic/rerender/effects.cy.jsx b/npm/react/cypress/component/basic/rerender/effects.cy.jsx index cebe663c7e08..b170d0ad6b9c 100644 --- a/npm/react/cypress/component/basic/rerender/effects.cy.jsx +++ b/npm/react/cypress/component/basic/rerender/effects.cy.jsx @@ -1,7 +1,6 @@ /// import React, { useLayoutEffect, useEffect } from 'react' -import ReactDom from 'react-dom' -import { mount, getContainerEl } from '@cypress/react' +import { mount } from '@cypress/react' it('should not run unmount effect cleanup when rerendering', () => { const layoutEffectCleanup = cy.stub() @@ -60,8 +59,8 @@ it('should run unmount effect cleanup when unmounting', () => { expect(effectCleanup).to.have.been.callCount(0) }) - cy - .then(() => ReactDom.unmountComponentAtNode(getContainerEl())) + // mount something else to trigger an unmount event + cy.mount(
    Hello
    ) .then(async () => { // does not call useEffect in react 17 unmount synchronously. // @see https://github.com/facebook/react/issues/20263 diff --git a/npm/react/cypress/component/basic/unmount/README.md b/npm/react/cypress/component/basic/unmount/README.md index 0b5361950e6f..a982c8864b63 100644 --- a/npm/react/cypress/component/basic/unmount/README.md +++ b/npm/react/cypress/component/basic/unmount/README.md @@ -1,21 +1,21 @@ # unmount -If you need to test what the component is doing when it is being unmounted, use `unmount` function. +If you need to test what the component is doing when it is being unmounted simply `mount` a blank component. ```js -import { mount, unmount } from '@cypress/react' +import { mount } from '@cypress/react' it('calls unmount prop', () => { // async command mount(...) - // cy commands - // now let's unmount (async command) - unmount() + // cy commands + // mount a blank component to unmount the previous component + mount(
    ) // confirm the component has been unmounted // and performed everything needed in its // componentWillUnmount method }) ``` -See [unmount-spec.js](unmount-spec.js) and [comp-spec.js](comp-spec.js) +See [comp-spec.js](comp-spec.js) diff --git a/npm/react/cypress/component/basic/unmount/comp.cy.jsx b/npm/react/cypress/component/basic/unmount/comp.cy.jsx index f12b80c56e6c..ea665eb674e7 100644 --- a/npm/react/cypress/component/basic/unmount/comp.cy.jsx +++ b/npm/react/cypress/component/basic/unmount/comp.cy.jsx @@ -1,7 +1,7 @@ /// -import Comp from './comp.jsx' +import { Comp } from './comp.jsx' import React from 'react' -import { mount, unmount } from '@cypress/react' +import { mount } from '@cypress/react' it('calls callbacks on mount and unmount', () => { const onMount = cy.stub() @@ -11,19 +11,14 @@ it('calls callbacks on mount and unmount', () => { mount() cy.then(() => { expect(onMount).to.have.been.calledOnce - expect(onUnmount).to.have.not.been.called }) cy.contains('Component with').should('be.visible') - let stub = cy.stub() + // mount something else to trigger unmount + mount(
    ) - try { - unmount() - } catch (e) { - expect(e.message).to.eq('`unmount` is no longer supported.') - stub() - } - - expect(stub).to.have.been.calledOnce + cy.then(() => { + expect(onUnmount).to.have.been.calledOnce + }) }) diff --git a/npm/react/cypress/component/basic/unmount/comp.jsx b/npm/react/cypress/component/basic/unmount/comp.jsx index 25122014f86f..ed5104b042fa 100644 --- a/npm/react/cypress/component/basic/unmount/comp.jsx +++ b/npm/react/cypress/component/basic/unmount/comp.jsx @@ -1,15 +1,11 @@ -import React, { Component } from 'react' +import React, { useEffect } from 'react' -export default class Comp extends Component { - componentDidMount () { - this.props.onMount() - } +export const Comp = ({ onMount, onUnmount }) => { + useEffect(() => { + onMount() - componentWillUnmount () { - this.props.onUnmount() - } + return onUnmount + }, []) - render () { - return
    Component with mount and unmount calls
    - } + return
    Component with mount and unmount calls
    } diff --git a/npm/react/cypress/component/basic/unmount/unmount.cy.jsx b/npm/react/cypress/component/basic/unmount/unmount.cy.jsx deleted file mode 100644 index e4bd608e723e..000000000000 --- a/npm/react/cypress/component/basic/unmount/unmount.cy.jsx +++ /dev/null @@ -1,45 +0,0 @@ -/// -import React, { Component } from 'react' -import { getContainerEl } from '@cypress/mount-utils' -import ReactDom from 'react-dom' -import { mount } from '@cypress/react' - -class Comp extends Component { - componentWillUnmount () { - // simply calls the prop - this.props.onUnmount() - } - - render () { - return
    My component
    - } -} - -describe('Comp with componentWillUnmount', () => { - it('calls the prop', () => { - mount() - cy.contains('My component') - - // after we have confirmed the component exists let's remove it - // unmount() command is automatically enqueued - cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl())) - - // the component is gone from the DOM - cy.contains('My component').should('not.exist') - // the component has called the prop on unmount - cy.get('@onUnmount').should('have.been.calledOnce') - }) - - it('can be called using then', () => { - mount() - cy.contains('My component') - - // still works, should probably be removed in v5 - cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl())) - - // the component is gone from the DOM - cy.contains('My component').should('not.exist') - // the component has called the prop on unmount - cy.get('@onUnmount').should('have.been.calledOnce') - }) -}) diff --git a/npm/react/cypress/component/basic/use-render/my-component.jsx b/npm/react/cypress/component/basic/use-render/my-component.jsx index 76a66262e66a..5fd5db9c3180 100644 --- a/npm/react/cypress/component/basic/use-render/my-component.jsx +++ b/npm/react/cypress/component/basic/use-render/my-component.jsx @@ -1,10 +1,7 @@ import React from 'react' -class MyComponent extends React.Component { - state = {} - render () { - return
    Hello
    - } +export function MyComponent () { + return
    Hello
    } export default MyComponent diff --git a/npm/react/cypress/component/removedMountingOptions.cy.jsx b/npm/react/cypress/component/removedMountingOptions.cy.jsx deleted file mode 100644 index 7fad6402b035..000000000000 --- a/npm/react/cypress/component/removedMountingOptions.cy.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import { mount } from '@cypress/react' - -describe('removed mounting options', () => { - function Foo () { - return (
    foo
    ) - } - - it('throws error when receiving removed mounting options', () => { - for (const key of ['cssFile', 'cssFiles', 'style', 'styles', 'stylesheet', 'stylesheets']) { - expect(() => mount(, { - [key]: `body { background: red; }`, - })).to.throw( - `The \`${key}\` mounting option is no longer supported.`, - ) - } - }) - - it('throws with custom command', () => { - Cypress.on('fail', (e) => { - expect(e.message).to.contain('The `styles` mounting option is no longer supported.') - - return false - }) - - cy.mount(, { - styles: 'body { background: red; }', - }) - }) -}) diff --git a/npm/react/examples.env b/npm/react/examples.env index 0c58d2262e01..3ff0dbaefec5 100644 --- a/npm/react/examples.env +++ b/npm/react/examples.env @@ -1,11 +1,8 @@ /examples/nextjs # /examples/nextjs-webpack-5 -/examples/react-scripts -/examples/react-scripts-typescript /examples/find-webpack /examples/webpack-file -/examples/react-scripts-folder /examples/using-babel-typescript /examples/webpack-options /examples/sass-and-ts diff --git a/npm/react/package.json b/npm/react/package.json index a0f4d42f429f..e6542d9ebd25 100644 --- a/npm/react/package.json +++ b/npm/react/package.json @@ -17,25 +17,26 @@ }, "devDependencies": { "@cypress/mount-utils": "0.0.0-development", - "@types/semver": "7.5.0", - "@vitejs/plugin-react": "4.3.0", - "axios": "0.21.2", + "@types/semver": "7.5.8", + "@vitejs/plugin-react": "4.3.3", + "axios": "1.7.7", "cypress": "0.0.0-development", - "prop-types": "15.7.2", - "react": "17.0.2", - "react-dom": "17.0.2", - "react-router": "6.10.0", - "react-router-dom": "6.10.0", + "prop-types": "15.8.1", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-router": "6.28.0", + "react-router-dom": "6.28.0", "semver": "^7.5.3", "typescript": "~5.4.5", - "vite": "5.2.11", + "vite": "5.4.10", "vite-plugin-require-transform": "1.0.12" }, "peerDependencies": { - "@types/react": "^16.9.16 || ^17.0.0", + "@types/react": "^18 || ^19", + "@types/react-dom": "^18 || ^19", "cypress": "*", - "react": "^=16.x || ^=17.x", - "react-dom": "^=16.x || ^=17.x" + "react": "^18 || ^19", + "react-dom": "^18 || ^19" }, "files": [ "dist" @@ -90,9 +91,6 @@ "nx": { "targets": { "build": { - "dependsOn": [ - "!@cypress/react18:build" - ], "outputs": [ "{workspaceRoot}/cli/react", "{projectRoot}/dist" diff --git a/npm/react/src/createMount.ts b/npm/react/src/createMount.ts index 6e87da8e68e7..ca708731a0ae 100644 --- a/npm/react/src/createMount.ts +++ b/npm/react/src/createMount.ts @@ -4,7 +4,6 @@ import { getContainerEl, ROOT_SELECTOR, setupHooks, - checkForRemovedStyleOptions, } from '@cypress/mount-utils' import type { InternalMountOptions, MountOptions, MountReturn, UnmountArgs } from './types' @@ -30,14 +29,6 @@ export const makeMountFn = ( throw Error('internalMountOptions must be provided with `render` and `reactDom` parameters') } - // @ts-expect-error - this is removed but we want to check if a user is passing it, and error if they are. - if (options.alias) { - // @ts-expect-error - Cypress.utils.throwErrByPath('mount.alias', options.alias) - } - - checkForRemovedStyleOptions(options) - mountCleanup = internalMountOptions.cleanup return cy @@ -81,10 +72,6 @@ export const makeMountFn = ( return cy.wrap({ component: userComponent, rerender: (newComponent) => makeMountFn('rerender', newComponent, options, key, internalMountOptions), - unmount: () => { - // @ts-expect-error - undocumented API - Cypress.utils.throwErrByPath('mount.unmount') - }, }, { log: false }) }) // by waiting, we delaying test execution for the next tick of event loop diff --git a/npm/react/src/index.ts b/npm/react/src/index.ts index 6bc437fa7577..3bababa408e3 100644 --- a/npm/react/src/index.ts +++ b/npm/react/src/index.ts @@ -2,6 +2,4 @@ export * from './createMount' export * from './mount' -export * from './mountHook' - export * from './types' diff --git a/npm/react/src/mount.ts b/npm/react/src/mount.ts index d4cb0fa21c93..735cdefb1264 100644 --- a/npm/react/src/mount.ts +++ b/npm/react/src/mount.ts @@ -1,23 +1,22 @@ -import { getContainerEl } from '@cypress/mount-utils' import React from 'react' -import ReactDOM from 'react-dom' -import major from 'semver/functions/major' +import ReactDOM from 'react-dom/client' +import { getContainerEl } from '@cypress/mount-utils' import { makeMountFn, makeUnmountFn, -} from './createMount' +} from './index' import type { MountOptions, InternalMountOptions, -} from './types' +} from './index' -let lastReactDom: typeof ReactDOM +let root: ReactDOM.Root | null const cleanup = () => { - if (lastReactDom) { - const root = getContainerEl() + if (root) { + root.unmount() - lastReactDom.unmountComponentAtNode(root) + root = null return true } @@ -27,10 +26,10 @@ const cleanup = () => { /** * Mounts a React component into the DOM. - * @param jsx {React.ReactNode} The React component to mount. - * @param options {MountOptions} [options={}] options to pass to the mount function. - * @param rerenderKey {string} [rerenderKey] A key to use to force a rerender. - * @see {@link https://on.cypress.io/mounting-react} for more details. + * @param {import('react').JSX.Element} jsx The React component to mount. + * @param {MountOptions} options Options to pass to the mount function. + * @param {string} rerenderKey A key to use to force a rerender. + * * @example * import { mount } from '@cypress/react' * import { Stepper } from './Stepper' @@ -40,24 +39,24 @@ const cleanup = () => { * cy.get('[data-cy=increment]').click() * cy.get('[data-cy=counter]').should('have.text', '1') * } + * + * @see {@link https://on.cypress.io/mounting-react} for more details. + * + * @returns {Cypress.Chainable} The mounted component. */ export function mount (jsx: React.ReactNode, options: MountOptions = {}, rerenderKey?: string) { - if (major(React.version) === 18) { - const message = '[cypress/react]: You are using `cypress/react`, which is designed for React <= 17. Consider changing to `cypress/react18`, which is designed for React 18.' - - console.error(message) - Cypress.log({ name: 'warning', message }) - } - // Remove last mounted component if cy.mount is called more than once in a test + // React by default removes the last component when calling render, but we should remove the root + // to wipe away any state cleanup() - const internalOptions: InternalMountOptions = { reactDom: ReactDOM, - render: (reactComponent: ReturnType, el: HTMLElement, reactDomToUse: typeof ReactDOM) => { - lastReactDom = (reactDomToUse || ReactDOM) + render: (reactComponent: ReturnType, el: HTMLElement) => { + if (!root) { + root = ReactDOM.createRoot(el) + } - return lastReactDom.render(reactComponent, el) + return root.render(reactComponent) }, unmount: internalUnmount, cleanup, @@ -66,25 +65,10 @@ export function mount (jsx: React.ReactNode, options: MountOptions = {}, rerende return makeMountFn('mount', jsx, { ReactDom: ReactDOM, ...options }, rerenderKey, internalOptions) } -/** - * Unmounts the component from the DOM. - * @internal - * @param options - Options for unmounting. - */ function internalUnmount (options = { log: true }) { return makeUnmountFn(options) } -/** - * Removed as of Cypress 11.0.0. - * @see https://on.cypress.io/migration-11-0-0-component-testing-updates - */ -export function unmount (options = { log: true }) { - // @ts-expect-error - undocumented API - Cypress.utils.throwErrByPath('mount.unmount') -} - -// Re-export this to help with migrating away from `unmount` export { getContainerEl, } diff --git a/npm/react/src/mountHook.ts b/npm/react/src/mountHook.ts deleted file mode 100644 index 9f04544d6b0f..000000000000 --- a/npm/react/src/mountHook.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Mounts a React hook function in a test component for testing. - * Removed as of Cypress 11.0.0. - * @see https://on.cypress.io/migration-11-0-0-component-testing-updates - */ -export const mountHook = (hookFn: (...args: any[]) => T) => { - // @ts-expect-error - internal API - Cypress.utils.throwErrByPath('mount.mount_hook') -} diff --git a/npm/react/src/types.ts b/npm/react/src/types.ts index b13bbcb1246f..a632ad0607d2 100644 --- a/npm/react/src/types.ts +++ b/npm/react/src/types.ts @@ -8,7 +8,7 @@ export interface UnmountArgs { export type MountOptions = Partial export interface MountReactComponentOptions { - ReactDom: typeof import('react-dom') + ReactDom: typeof import('react-dom/client') /** * Log the mounting command into Cypress Command Log, * true by default. @@ -22,11 +22,11 @@ export interface MountReactComponentOptions { } export interface InternalMountOptions { - reactDom: typeof import('react-dom') + reactDom: typeof import('react-dom/client') render: ( reactComponent: ReturnType, el: HTMLElement, - reactDomToUse: typeof import('react-dom') + reactDomToUse: typeof import('react-dom/client') ) => void unmount: (options: UnmountArgs) => void cleanup: () => boolean @@ -44,11 +44,4 @@ export interface MountReturn { * or have asynchronous updates (`useEffect`, `useLayoutEffect`). */ rerender: (component: React.ReactNode) => globalThis.Cypress.Chainable - /** - * Removes the mounted component. - * - * Removed as of Cypress 11.0.0. - * @see https://on.cypress.io/migration-11-0-0-component-testing-updates - */ - unmount: (payload: UnmountArgs) => void // globalThis.Cypress.Chainable> } diff --git a/npm/react18/.eslintignore b/npm/react18/.eslintignore deleted file mode 100644 index 79afe972da7d..000000000000 --- a/npm/react18/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -**/dist -**/*.d.ts -**/package-lock.json -**/tsconfig.json -**/cypress/fixtures \ No newline at end of file diff --git a/npm/react18/.releaserc.js b/npm/react18/.releaserc.js deleted file mode 100644 index 17d3bb871472..000000000000 --- a/npm/react18/.releaserc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - ...require('../../.releaserc'), -} diff --git a/npm/react18/CHANGELOG.md b/npm/react18/CHANGELOG.md deleted file mode 100644 index 8afdf6422cc5..000000000000 --- a/npm/react18/CHANGELOG.md +++ /dev/null @@ -1,47 +0,0 @@ -# [@cypress/react18-v2.0.1](https://github.com/cypress-io/cypress/compare/@cypress/react18-v2.0.0...@cypress/react18-v2.0.1) (2024-06-07) - - -### Bug Fixes - -* update cypress to Typescript 5 ([#29568](https://github.com/cypress-io/cypress/issues/29568)) ([f3b6766](https://github.com/cypress-io/cypress/commit/f3b67666a5db0438594339c379cf27e1fd1e4abc)) - -# [@cypress/react18-v2.0.0](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.1.1...@cypress/react18-v2.0.0) (2022-11-07) - - -### Bug Fixes - -* remove last mounted component upon subsequent mount calls ([#24470](https://github.com/cypress-io/cypress/issues/24470)) ([f39eb1c](https://github.com/cypress-io/cypress/commit/f39eb1c19e0923bda7ae263168fc6448da942d54)) -* remove some CT functions and props ([#24419](https://github.com/cypress-io/cypress/issues/24419)) ([294985f](https://github.com/cypress-io/cypress/commit/294985f8b3e0fa00ed66d25f88c8814603766074)) - - -### BREAKING CHANGES - -* remove last mounted component upon subsequent mount calls of mount - -# [@cypress/react18-v1.1.1](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.1.0...@cypress/react18-v1.1.1) (2022-10-13) - - -### Bug Fixes - -* cypress/react18 rerender ([#23360](https://github.com/cypress-io/cypress/issues/23360)) ([8b8f20e](https://github.com/cypress-io/cypress/commit/8b8f20eec77d4c0a704aee7f7077dc92dbafb93f)) - -# [@cypress/react18-v1.1.0](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.0.1...@cypress/react18-v1.1.0) (2022-08-30) - - -### Features - -* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8)) - -# [@cypress/react18-v1.0.1](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.0.0...@cypress/react18-v1.0.1) (2022-08-15) - - -### Bug Fixes - -* **react18:** unmount component with react18 API ([#23204](https://github.com/cypress-io/cypress/issues/23204)) ([eab950b](https://github.com/cypress-io/cypress/commit/eab950bec013f9caf5836e3fa58670fde25e2684)) - -# @cypress/react18-v1.0.0 (2022-08-11) - - -### Features - -* React 18 support ([#22876](https://github.com/cypress-io/cypress/issues/22876)) ([f0d3a48](https://github.com/cypress-io/cypress/commit/f0d3a4867907bf6e60468510daa883ccc8dcfb63)) diff --git a/npm/react18/README.md b/npm/react18/README.md deleted file mode 100644 index 4f85d8081af8..000000000000 --- a/npm/react18/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# @cypress/react18 - -Mount React 18 components in the open source [Cypress.io](https://www.cypress.io/) test runner - -> **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [React Component Testing Docs](https://docs.cypress.io/guides/component-testing/react/overview) for mounting React components. Installing and importing `mount` from `@cypress/react18` should only be done for advanced use-cases. - -## [Changelog](./CHANGELOG.md) diff --git a/npm/react18/package.json b/npm/react18/package.json deleted file mode 100644 index 5104e75eb943..000000000000 --- a/npm/react18/package.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "@cypress/react18", - "version": "0.0.0-development", - "description": "Test React components using Cypress", - "main": "dist/cypress-react.cjs.js", - "scripts": { - "build": "rimraf dist && rollup -c rollup.config.mjs", - "postbuild": "node ../../scripts/sync-exported-npm-with-cli.js", - "check-ts": "tsc --noEmit", - "lint": "eslint --ext .js,.jsx,.ts,.tsx,.json, .", - "watch": "yarn build --watch --watch.exclude ./dist/**/*" - }, - "devDependencies": { - "@cypress/mount-utils": "0.0.0-development", - "@cypress/react": "0.0.0-development", - "@rollup/plugin-commonjs": "^17.1.0", - "@rollup/plugin-node-resolve": "^11.1.1", - "@types/react": "17.0.83", - "@types/react-dom": "17.0.25", - "cypress": "0.0.0-development", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "rollup": "3.7.3", - "rollup-plugin-typescript2": "^0.29.0", - "typescript": "~5.4.5" - }, - "peerDependencies": { - "@types/react": "^18", - "@types/react-dom": "^18", - "cypress": "*", - "react": "^18", - "react-dom": "^18" - }, - "files": [ - "dist" - ], - "types": "dist/index.d.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/cypress-io/cypress.git" - }, - "homepage": "https://github.com/cypress-io/cypress/blob/develop/npm/react18/#readme", - "bugs": "https://github.com/cypress-io/cypress/issues/new?assignees=&labels=npm%3A%20%40cypress%2Freact18&template=1-bug-report.md&title=", - "keywords": [ - "react", - "cypress", - "cypress-io", - "test", - "testing" - ], - "module": "dist/cypress-react.esm-bundler.js", - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - }, - "publishConfig": { - "access": "public" - }, - "nx": { - "targets": { - "build": { - "outputs": [ - "{workspaceRoot}/cli/react18", - "{projectRoot}/dist" - ] - } - } - } -} diff --git a/npm/react18/rollup.config.mjs b/npm/react18/rollup.config.mjs deleted file mode 100644 index db047e2bbd48..000000000000 --- a/npm/react18/rollup.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import rollupConfig from '@cypress/react/rollup.config.mjs' - -export default rollupConfig diff --git a/npm/react18/src/index.ts b/npm/react18/src/index.ts deleted file mode 100644 index 4d5c3f6766df..000000000000 --- a/npm/react18/src/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react' -// @ts-expect-error -import ReactDOM from 'react-dom/client' -import { getContainerEl } from '@cypress/mount-utils' -import { - makeMountFn, - makeUnmountFn, -} from '@cypress/react' -import type { - MountOptions, - MountReturn, - InternalMountOptions, - UnmountArgs, -} from '@cypress/react' - -let root: ReactDOM.Root | null - -const cleanup = () => { - if (root) { - root.unmount() - - root = null - - return true - } - - return false -} - -/** - * Mounts a React component into the DOM. - * @param {import('react').JSX.Element} jsx The React component to mount. - * @param {MountOptions} options Options to pass to the mount function. - * @param {string} rerenderKey A key to use to force a rerender. - * - * @example - * import { mount } from '@cypress/react' - * import { Stepper } from './Stepper' - * - * it('mounts', () => { - * mount() - * cy.get('[data-cy=increment]').click() - * cy.get('[data-cy=counter]').should('have.text', '1') - * } - * - * @see {@link https://on.cypress.io/mounting-react} for more details. - * - * @returns {Cypress.Chainable} The mounted component. - */ -export function mount (jsx: React.ReactNode, options: MountOptions = {}, rerenderKey?: string) { - // Remove last mounted component if cy.mount is called more than once in a test - // React by default removes the last component when calling render, but we should remove the root - // to wipe away any state - cleanup() - const internalOptions: InternalMountOptions = { - reactDom: ReactDOM, - render: (reactComponent: ReturnType, el: HTMLElement) => { - if (!root) { - root = ReactDOM.createRoot(el) - } - - return root.render(reactComponent) - }, - unmount: internalUnmount, - cleanup, - } - - return makeMountFn('mount', jsx, { ReactDom: ReactDOM, ...options }, rerenderKey, internalOptions) -} - -function internalUnmount (options = { log: true }) { - return makeUnmountFn(options) -} -/** - * Removed as of Cypress 11.0.0. - * @see https://on.cypress.io/migration-11-0-0-component-testing-updates - */ -export function unmount (options: UnmountArgs = { log: true }) { - // @ts-expect-error - undocumented API - Cypress.utils.throwErrByPath('mount.unmount') -} - -// Re-export this to help with migrating away from `unmount` -export { - getContainerEl, -} - -export type { - MountOptions, - MountReturn, -} diff --git a/npm/react18/tsconfig.json b/npm/react18/tsconfig.json deleted file mode 100644 index 8e6ae3e4c8a9..000000000000 --- a/npm/react18/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "esnext", - "moduleResolution": "node", - "lib": [ - "es2015", - "dom" - ], - "rootDir": "src", - "outDir": "dist", - "declaration": true, - "strict": true, - "types": [ - "cypress" - ], - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "resolveJsonModule": false - }, - "include": ["src/**/*.ts"] -} diff --git a/npm/svelte/README.md b/npm/svelte/README.md index 72c89f159fae..caabccf29d73 100644 --- a/npm/svelte/README.md +++ b/npm/svelte/README.md @@ -1,8 +1,12 @@ # @cypress/svelte -Mount Svelte components in the open source [Cypress.io](https://www.cypress.io/) test runner **v10.7.0+** +Mount Svelte components in the open source [Cypress.io](https://www.cypress.io/) test runner -> **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [Svelte Component Testing Docs](https://docs.cypress.io/guides/component-testing/svelte/overview) for mounting Svelte components. Installing and importing `mount` from `@cypress/svelte` should only be done for advanced use-cases. +> **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [Svelte Component Testing Docs](https://docs.cypress.io/guides/component-testing/svelte/overview) for mounting Svelte components. Installing and importing `mount` from `@cypress/svelte` should only be done for advanced use-cases or in the case you may require an older or non supported version of svelte. + +## Requirements + +- Svelte 5+ (Cypress 13 and under supports Svelte 4 and under) ## Development diff --git a/npm/svelte/package.json b/npm/svelte/package.json index 6497ecb1377a..2d871996fe95 100644 --- a/npm/svelte/package.json +++ b/npm/svelte/package.json @@ -12,12 +12,12 @@ }, "devDependencies": { "@cypress/mount-utils": "0.0.0-development", - "svelte": "^3.49.0", + "svelte": "^5.4.0", "typescript": "~5.4.5" }, "peerDependencies": { "cypress": ">=10.6.0", - "svelte": ">=3.0.0" + "svelte": ">=5.0.0" }, "files": [ "dist/**/*" diff --git a/npm/svelte/rollup.config.mjs b/npm/svelte/rollup.config.mjs index 18d161ab7f90..f84e6b04b5aa 100644 --- a/npm/svelte/rollup.config.mjs +++ b/npm/svelte/rollup.config.mjs @@ -1,3 +1,10 @@ import { createEntries } from '@cypress/mount-utils/create-rollup-entry.mjs' -export default createEntries({ formats: ['es', 'cjs'], input: 'src/index.ts' }) +const config = { + external: [ + 'svelte', + ], +} + +// updated respectExternal to false due to this issue: https://github.com/Swatinem/rollup-plugin-dts/issues/162#issuecomment-1702374232 +export default createEntries({ formats: ['es', 'cjs'], input: 'src/index.ts', dtsOptions: { respectExternal: false }, config }) diff --git a/npm/svelte/src/mount.ts b/npm/svelte/src/mount.ts index 97c073b266d7..1e651a25026c 100644 --- a/npm/svelte/src/mount.ts +++ b/npm/svelte/src/mount.ts @@ -1,35 +1,26 @@ import { getContainerEl, setupHooks, - checkForRemovedStyleOptions, } from '@cypress/mount-utils' -import type { ComponentConstructorOptions, ComponentProps, SvelteComponent } from 'svelte' +import { mount as svelteMount, unmount as svelteUnmount } from 'svelte' +import type { MountOptions, Component } from 'svelte' const DEFAULT_COMP_NAME = 'unknown' -type SvelteConstructor = new (...args: any[]) => T; -type SvelteComponentOptions = Omit< - ComponentConstructorOptions>, - 'hydrate' | 'target' | '$$inline' ->; - -export interface MountOptions - extends SvelteComponentOptions { - log?: boolean -} - -export interface MountReturn { - component: T +export interface MountReturn{ + component: Record } -let componentInstance: SvelteComponent | undefined +let componentInstance: Record | undefined const cleanup = () => { - componentInstance?.$destroy() + if (componentInstance) { + svelteUnmount(componentInstance) + } } // Extract the component name from the object passed to mount -const getComponentDisplayName = (Component: SvelteConstructor): string => { +const getComponentDisplayName = (Component: Component, Record, any>): string => { if (Component.name) { const [, match] = /Proxy\<(\w+)\>/.exec(Component.name) || [] @@ -42,7 +33,7 @@ const getComponentDisplayName = (Component: SvelteCon /** * Mounts a Svelte component inside the Cypress browser * - * @param {SvelteConstructor} Component Svelte component being mounted + * @param {Record} Component Svelte component being mounted * @param {MountReturn} options options to customize the component being mounted * @returns Cypress.Chainable * @@ -57,11 +48,13 @@ const getComponentDisplayName = (Component: SvelteCon * * @see {@link https://on.cypress.io/mounting-svelte} for more details. */ -export function mount ( - Component: SvelteConstructor, - options: MountOptions = {}, -): Cypress.Chainable> { - checkForRemovedStyleOptions(options) +export function mount ( + Component: Component, Record, any>, + options: Omit & {log?: boolean} = {}, +): Cypress.Chainable { + // In Svelte 5, the component name is no longer easily discoverable and logs as "wrapper" + // so we default the logging of it to false as it doesn't provide a lot of value + options.log = options.log || false return cy.then(() => { // Remove last mounted component if cy.mount is called more than once in a test @@ -69,9 +62,10 @@ export function mount ( const target = getContainerEl() - const ComponentConstructor = ((Component as any).default || Component) as SvelteConstructor + const ComponentConstructor = ((Component as any).default || Component) as Component, Record, any> - componentInstance = new ComponentConstructor({ + // @see https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes + componentInstance = svelteMount(ComponentConstructor, { target, ...options, }) @@ -88,7 +82,7 @@ export function mount ( }) } }) - .wrap({ component: componentInstance as T }, { log: false }) + .wrap({ component: componentInstance as Record }, { log: false }) }) } diff --git a/npm/vite-dev-server/README.md b/npm/vite-dev-server/README.md index b0824e8567e3..66e47d1b8d1f 100644 --- a/npm/vite-dev-server/README.md +++ b/npm/vite-dev-server/README.md @@ -12,7 +12,7 @@ import { defineConfig } from 'cypress' export default defineConfig({ component: { devServer: { - framework: 'create-react-app', + framework: 'react', bundler: 'vite', // viteConfig?: Will try to infer, if passed it will be used as is } @@ -51,10 +51,11 @@ We then merge the sourced config with the user's vite config, and layer on our o ## Compatibility -| @cypress/vite-dev-server | cypress | -| ------------------------ | ------- | -| <= v2 | <= v9 | -| >= v3 | >= v10 | +| @cypress/vite-dev-server | cypress | +| ------------------------ | ------------- | +| <= v2 | <= v9 | +| >= v3 <= v5 | >= v10 <= v13 | +| >= v6 | >= v14 | #### `devServerPublicPathRoute` for Vite v5 diff --git a/npm/vite-dev-server/cypress/e2e/react.cy.ts b/npm/vite-dev-server/cypress/e2e/react.cy.ts index b42eee9231d4..8fb93925b879 100644 --- a/npm/vite-dev-server/cypress/e2e/react.cy.ts +++ b/npm/vite-dev-server/cypress/e2e/react.cy.ts @@ -4,7 +4,7 @@ import dedent from 'dedent' type ProjectDirs = typeof fixtureDirs -const VITE_REACT: ProjectDirs[number][] = ['vite2.8.6-react', 'vite2.9.1-react'] +const VITE_REACT: ProjectDirs[number][] = ['vite4.5.5-react', 'vite5.4.10-react', 'vite6.0.0-react'] // Add to this list to focus on a particular permutation const ONLY_PROJECTS: ProjectDirs[number][] = [] diff --git a/npm/vite-dev-server/cypress/e2e/vite-dev-server.cy.ts b/npm/vite-dev-server/cypress/e2e/vite-dev-server.cy.ts index 8a7d64aceb81..dd3862728753 100644 --- a/npm/vite-dev-server/cypress/e2e/vite-dev-server.cy.ts +++ b/npm/vite-dev-server/cypress/e2e/vite-dev-server.cy.ts @@ -19,7 +19,7 @@ describe('Config options', () => { await ctx.actions.file.writeFileInProject( 'src/App.cy.jsx', ` import React from 'react' - import { mount } from 'cypress/react18' + import { mount } from 'cypress/react' export const App = () => { return ( @@ -44,22 +44,23 @@ describe('Config options', () => { }) it('supports supportFile = false', () => { - cy.scaffoldProject('vite2.9.1-react') - cy.openProject('vite2.9.1-react', ['--config-file', 'cypress-vite-no-support.config.ts', '--component']) + cy.scaffoldProject('vite6.0.0-react') + cy.openProject('vite6.0.0-react', ['--config-file', 'cypress-vite-no-support.config.ts', '--component']) cy.startAppServer('component') cy.visitApp() cy.specsPageIsVisible() cy.contains('App.cy.jsx').click() cy.waitForSpecToFinish() - cy.get('.passed > .num').should('contain', 1) + // no support file means there is no mount function registered, so all tests should fail + cy.get('.failed > .num').should('contain', 2) }) it('supports serving files with whitespace', () => { const specWithWhitespace = 'spec with whitespace.cy.jsx' - cy.scaffoldProject('vite2.9.1-react') - cy.openProject('vite2.9.1-react', ['--config-file', 'cypress-vite.config.ts', '--component']) + cy.scaffoldProject('vite6.0.0-react') + cy.openProject('vite6.0.0-react', ['--config-file', 'cypress-vite.config.ts', '--component']) cy.startAppServer('component') cy.withCtx(async (ctx, { specWithWhitespace }) => { @@ -76,8 +77,8 @@ describe('Config options', () => { }) it('supports @cypress/vite-dev-server', () => { - cy.scaffoldProject('vite2.9.1-react') - cy.openProject('vite2.9.1-react', ['--config-file', 'cypress-vite-dev-server-function.config.ts', '--component']) + cy.scaffoldProject('vite6.0.0-react') + cy.openProject('vite6.0.0-react', ['--config-file', 'cypress-vite-dev-server-function.config.ts', '--component']) cy.startAppServer('component') cy.visitApp() @@ -88,8 +89,8 @@ describe('Config options', () => { }) it('supports viteConfig as an async function', () => { - cy.scaffoldProject('vite2.9.1-react') - cy.openProject('vite2.9.1-react', ['--config-file', 'cypress-vite-async-function-config.config.ts', '--component']) + cy.scaffoldProject('vite6.0.0-react') + cy.openProject('vite6.0.0-react', ['--config-file', 'cypress-vite-async-function-config.config.ts', '--component']) cy.startAppServer('component') cy.visitApp() @@ -119,8 +120,8 @@ describe('sourcemaps', () => { }) ` - cy.scaffoldProject('vite3.0.2-react') - cy.openProject('vite3.0.2-react', ['--config-file', 'cypress-vite.config.ts', '--component']) + cy.scaffoldProject('vite6.0.0-react') + cy.openProject('vite6.0.0-react', ['--config-file', 'cypress-vite.config.ts', '--component']) cy.startAppServer('component') cy.withCtx(async (ctx, o) => { @@ -159,9 +160,9 @@ describe('sourcemaps', () => { cy.get('.runnable-err-file-path', { timeout: 250 }).should('contain', `${specName}:${line}:${column}`) } - verifySourcemap('JsErrorSpec.cy.js', 7, 9) + verifySourcemap('JsErrorSpec.cy.js', 7, 8) - verifySourcemap('JsWithImportErrorSpec.cy.js', 9, 9) + verifySourcemap('JsWithImportErrorSpec.cy.js', 9, 8) verifySourcemap('JsxErrorSpec.cy.jsx', 7, 8) diff --git a/npm/vite-dev-server/package.json b/npm/vite-dev-server/package.json index f23ea8b4c369..30b42466c189 100644 --- a/npm/vite-dev-server/package.json +++ b/npm/vite-dev-server/package.json @@ -29,6 +29,7 @@ "ts-node": "^10.9.2", "vite-4": "npm:vite@^4.5.3", "vite-5": "npm:vite@^5.2.8", + "vite-6": "npm:vite@^6.0.0", "vite-plugin-inspect": "0.8.4" }, "files": [ diff --git a/npm/vite-dev-server/src/devServer.ts b/npm/vite-dev-server/src/devServer.ts index 58b89c0996cd..57cfa6d29e0f 100644 --- a/npm/vite-dev-server/src/devServer.ts +++ b/npm/vite-dev-server/src/devServer.ts @@ -1,6 +1,6 @@ import debugFn from 'debug' import semverMajor from 'semver/functions/major' -import type { UserConfig } from 'vite-5' +import type { UserConfig } from 'vite-6' import { getVite, Vite } from './getVite' import { createViteDevServerConfig } from './resolveConfig' diff --git a/npm/vite-dev-server/src/getVite.ts b/npm/vite-dev-server/src/getVite.ts index b324ea35abad..f716808d38f3 100644 --- a/npm/vite-dev-server/src/getVite.ts +++ b/npm/vite-dev-server/src/getVite.ts @@ -3,7 +3,7 @@ import type { ViteDevServerConfig } from './devServer' const debug = debugFn('cypress:vite-dev-server:getVite') -export type Vite = typeof import('vite-5') +export type Vite = typeof import('vite-6') // "vite-dev-server" is bundled in the binary, so we need to require.resolve "vite" // from root of the active project since we don't bundle vite internally but rather diff --git a/npm/vite-dev-server/src/plugins/cypress.ts b/npm/vite-dev-server/src/plugins/cypress.ts index 25bf7c9a5a28..7d85fdd07b97 100644 --- a/npm/vite-dev-server/src/plugins/cypress.ts +++ b/npm/vite-dev-server/src/plugins/cypress.ts @@ -1,5 +1,5 @@ import debugFn from 'debug' -import type { ModuleNode, PluginOption, ViteDevServer } from 'vite-5' +import type { ModuleNode, PluginOption, ViteDevServer } from 'vite-6' import type { Vite } from '../getVite' import { parse, HTMLElement } from 'node-html-parser' import fs from 'fs' @@ -42,7 +42,12 @@ export const Cypress = ( // eslint-disable-next-line no-restricted-syntax let loader = fs.readFileSync(INIT_FILEPATH, 'utf8') - devServerEvents.on('dev-server:specs:changed', (specs: Spec[]) => { + devServerEvents.on('dev-server:specs:changed', ({ specs, options }: { specs: Spec[], options?: { neededForJustInTimeCompile: boolean }}) => { + if (options?.neededForJustInTimeCompile) { + // if an option is needed for just in time compile, no-op as this isn't supported in vite + return + } + debug(`dev-server:secs:changed: ${specs.map((spec) => spec.relative)}`) specsPathsSet = getSpecsPathsSet(specs) }) @@ -94,13 +99,7 @@ export const Cypress = ( }, configureServer: async (server: ViteDevServer) => { server.middlewares.use(`${base}index.html`, async (req, res) => { - let transformedIndexHtml = await server.transformIndexHtml(base, '') - const viteImport = `` - - // If we're doing cy-in-cy, we need to be able to access the Cypress instance from the parent frame. - if (process.env.CYPRESS_INTERNAL_VITE_OPEN_MODE_TESTING) { - transformedIndexHtml = transformedIndexHtml.replace(viteImport, `${viteImport}`) - } + const transformedIndexHtml = await server.transformIndexHtml(base, '') return res.end(transformedIndexHtml) }) diff --git a/npm/vite-dev-server/src/plugins/sourcemap.ts b/npm/vite-dev-server/src/plugins/sourcemap.ts index 003e51cbcbd9..2bda5291bfac 100644 --- a/npm/vite-dev-server/src/plugins/sourcemap.ts +++ b/npm/vite-dev-server/src/plugins/sourcemap.ts @@ -1,5 +1,5 @@ import debugFn from 'debug' -import type { PluginOption } from 'vite-5' +import type { PluginOption } from 'vite-6' import type { Vite } from '../getVite' import type { ViteDevServerConfig } from '../devServer' diff --git a/npm/vite-dev-server/src/resolveConfig.ts b/npm/vite-dev-server/src/resolveConfig.ts index 10dfd5259290..3ce950bd6473 100644 --- a/npm/vite-dev-server/src/resolveConfig.ts +++ b/npm/vite-dev-server/src/resolveConfig.ts @@ -4,7 +4,7 @@ * You can find it here https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/create.ts */ import debugFn from 'debug' -import type { InlineConfig } from 'vite-5' +import type { InlineConfig } from 'vite-6' import path from 'path' import semverGte from 'semver/functions/gte' @@ -65,7 +65,6 @@ export const createViteDevServerConfig = async (config: ViteDevServerConfig, vit function makeCypressViteConfig (config: ViteDevServerConfig, vite: Vite): InlineConfig | InlineConfig { const { cypressConfig: { - experimentalJustInTimeCompile, port, projectRoot, devServerPublicPathRoute, @@ -129,8 +128,7 @@ function makeCypressViteConfig (config: ViteDevServerConfig, vite: Vite): Inline port: vitePort, host: '127.0.0.1', // Disable file watching and HMR when executing tests in `run` mode - // if experimentalJustInTimeCompile is configured, we need to watch for file changes as the spec entries are going to be updated per test in run mode - ...(isTextTerminal && !experimentalJustInTimeCompile + ...(isTextTerminal ? { watch: { ignored: '**/*' }, hmr: false } : {}), }, diff --git a/npm/vite-dev-server/test/resolveConfig.spec.ts b/npm/vite-dev-server/test/resolveConfig.spec.ts index 0963a5ccd95e..f027a8b28a8d 100644 --- a/npm/vite-dev-server/test/resolveConfig.spec.ts +++ b/npm/vite-dev-server/test/resolveConfig.spec.ts @@ -2,6 +2,7 @@ import Chai, { expect } from 'chai' import { EventEmitter } from 'events' import * as vite4 from 'vite-4' import * as vite5 from 'vite-5' +import * as vite6 from 'vite-6' import { scaffoldSystemTestProject } from './test-helpers/scaffoldProject' import { createViteDevServerConfig } from '../src/resolveConfig' import sinon from 'sinon' @@ -22,7 +23,7 @@ const getViteDevServerConfig = (projectRoot: string) => { framework: 'react', } as unknown as ViteDevServerConfig } -const MAJOR_VERSIONS: ({version: 4, vite: any } | {version: 5, vite: any })[] = [ +const MAJOR_VERSIONS: ({version: 4, vite: any } | {version: 5, vite: any } | {version: 6, vite: any })[] = [ { version: 4, vite: vite4, @@ -31,6 +32,10 @@ const MAJOR_VERSIONS: ({version: 4, vite: any } | {version: 5, vite: any })[] = version: 5, vite: vite5, }, + { + version: 6, + vite: vite6, + }, ] describe('resolveConfig', function () { @@ -109,40 +114,5 @@ describe('resolveConfig', function () { expect(viteConfig.server?.hmr).to.be.undefined }) }) - - describe('experimentalJustInTimeCompile', () => { - let viteDevServerConfig: ViteDevServerConfig - - beforeEach(async () => { - const projectRoot = await scaffoldSystemTestProject(`vite${version}-inspect`) - - viteDevServerConfig = getViteDevServerConfig(projectRoot) - viteDevServerConfig.cypressConfig.experimentalJustInTimeCompile = true - }) - - describe('open mode', () => { - beforeEach(() => { - viteDevServerConfig.cypressConfig.isTextTerminal = false - }) - - it('enables hmr and watching', async () => { - const viteConfig = await createViteDevServerConfig(viteDevServerConfig, discoveredVite) - - expect(viteConfig.server.watch).to.be.undefined - }) - }) - - describe('run mode', () => { - beforeEach(() => { - viteDevServerConfig.cypressConfig.isTextTerminal = true - }) - - it('enables hmr and watching', async () => { - const viteConfig = await createViteDevServerConfig(viteDevServerConfig, discoveredVite) - - expect(viteConfig.server.watch).to.be.undefined - }) - }) - }) }) }) diff --git a/npm/vite-plugin-cypress-esm/README.md b/npm/vite-plugin-cypress-esm/README.md index 97cb3aed2d5a..422b211f1b1b 100644 --- a/npm/vite-plugin-cypress-esm/README.md +++ b/npm/vite-plugin-cypress-esm/README.md @@ -10,8 +10,8 @@ Run Cypress with `DEBUG=cypress:vite-plugin-cypress-esm`. You will get logs in t ## Compatibility | @cypress/vite-plugin-cypress-esm | cypress | -| ------------------------ | ------- | -| >= v1 | >= v12 | +| -------------------------------- | ------- | +| >= v1 | >= v12 | ## Usage @@ -80,6 +80,14 @@ CypressEsm({ }) ``` +If using the `@cypress/react` test harness, you may need to ignore the `react-dom/client` module by configuring as such: + +```ts +CypressEsm({ + ignoreImportList: ['**/react-dom/client'] +}) +``` + ## Known Issues ### Import Syntax diff --git a/npm/vite-plugin-cypress-esm/cypress.config.ts b/npm/vite-plugin-cypress-esm/cypress.config.ts index 3ea2aa0a48f5..88778d7d2290 100644 --- a/npm/vite-plugin-cypress-esm/cypress.config.ts +++ b/npm/vite-plugin-cypress-esm/cypress.config.ts @@ -13,12 +13,11 @@ export default defineConfig({ viteConfig: () => { return { plugins: [ - react({ - jsxRuntime: 'classic', - }), + react(), CypressEsm({ ignoreModuleList: ['**/ignoreModuleList.cy.ts', '*MyAsync*'], - ignoreImportList: ['**/ImmutableModuleB*'], + // For `cypress/react` on react 18+, we need to ignore transforming the react-dom/client library + ignoreImportList: ['**/ImmutableModuleB*', '**/react-dom/client'], }), ], } diff --git a/npm/vite-plugin-cypress-esm/package.json b/npm/vite-plugin-cypress-esm/package.json index d7b5f214f551..f111d493a2b7 100644 --- a/npm/vite-plugin-cypress-esm/package.json +++ b/npm/vite-plugin-cypress-esm/package.json @@ -19,11 +19,11 @@ "devDependencies": { "@tanstack/react-query": "4.36.1", "@types/picomatch": "2.3.0", - "@vitejs/plugin-react": "4.3.0", - "react": "16.8.6", - "react-dom": "16.8.6", - "react-router": "6.10.0", - "react-router-dom": "6.10.0", + "@vitejs/plugin-react": "4.3.3", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-router": "6.28.0", + "react-router-dom": "6.28.0", "vite": "5.2.11" }, "files": [ diff --git a/npm/vue/cypress.config.ts b/npm/vue/cypress.config.ts index d4c8b1be979c..35f6d7cc2298 100644 --- a/npm/vue/cypress.config.ts +++ b/npm/vue/cypress.config.ts @@ -5,7 +5,6 @@ export default defineConfig({ 'viewportHeight': 500, 'responseTimeout': 2500, 'projectId': '134ej7', - 'experimentalFetchPolyfill': true, 'e2e': { 'supportFile': false, }, diff --git a/npm/vue/cypress/component/advanced/mocking-axios/1-Users.cy.js b/npm/vue/cypress/component/advanced/mocking-axios/1-Users.cy.js index c2f678ffc6ed..6488b89c460d 100644 --- a/npm/vue/cypress/component/advanced/mocking-axios/1-Users.cy.js +++ b/npm/vue/cypress/component/advanced/mocking-axios/1-Users.cy.js @@ -6,9 +6,10 @@ import mockUsers from './user.list.json' // import everything from "axios" module // so we can mock its methods from the test -const Axios = require('axios') +import Axios from 'axios' -describe('Mocking get import from Axios', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Mocking get import from Axios', () => { it('renders mocked data', () => { cy.stub(Axios, 'get') .resolves({ diff --git a/npm/vue/cypress/component/advanced/mocking-axios/2-Users.cy.js b/npm/vue/cypress/component/advanced/mocking-axios/2-Users.cy.js index 3646adda409a..6ac8c7ad1838 100644 --- a/npm/vue/cypress/component/advanced/mocking-axios/2-Users.cy.js +++ b/npm/vue/cypress/component/advanced/mocking-axios/2-Users.cy.js @@ -6,9 +6,10 @@ import mockUsers from './user.list.json' // import everything from "axios" module // so we can mock its methods from the test -const Axios = require('axios') +import Axios from 'axios' -describe('Mocking get import from Axios', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Mocking get import from Axios', () => { it('renders mocked data', () => { cy.stub(Axios, 'get') .resolves({ diff --git a/npm/vue/cypress/component/advanced/mocking-axios/3-Users.cy.js b/npm/vue/cypress/component/advanced/mocking-axios/3-Users.cy.js deleted file mode 100644 index e5bc00d14cb9..000000000000 --- a/npm/vue/cypress/component/advanced/mocking-axios/3-Users.cy.js +++ /dev/null @@ -1,27 +0,0 @@ -/// -import { mount } from '@cypress/vue' -import Users from './3-Users.vue' -// test file can import the entire AxiosApi module -import * as AxiosApi from './AxiosApi' - -// TODO: esmodule mocking is broken -xdescribe('Mocking imports from Axios Wrapper', () => { - it('renders mocked data', () => { - // stub export "get" that Users component imports and uses - cy.stub(AxiosApi, 'get') - .resolves({ - data: [ - { - id: 101, - name: 'Test User', - }, - ], - }) - .as('get') - - mount(Users) - // the mock response is used 😀 - cy.get('li').should('have.length', 1) - cy.get('@get').should('have.been.calledOnce') - }) -}) diff --git a/npm/vue/cypress/component/advanced/mocking-axios/3-Users.vue b/npm/vue/cypress/component/advanced/mocking-axios/3-Users.vue deleted file mode 100644 index ee90dd655932..000000000000 --- a/npm/vue/cypress/component/advanced/mocking-axios/3-Users.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - diff --git a/npm/vue/cypress/component/advanced/mocking-axios/AxiosApi.js b/npm/vue/cypress/component/advanced/mocking-axios/AxiosApi.js deleted file mode 100644 index c4750a0ee8c1..000000000000 --- a/npm/vue/cypress/component/advanced/mocking-axios/AxiosApi.js +++ /dev/null @@ -1,2 +0,0 @@ -// an intermediate wrapper around "axios" CommonJS module -export * from 'axios' diff --git a/npm/vue/cypress/component/alert/alert.cy.js b/npm/vue/cypress/component/alert/alert.cy.js index d53bc6def994..1afd482bd3a4 100644 --- a/npm/vue/cypress/component/alert/alert.cy.js +++ b/npm/vue/cypress/component/alert/alert.cy.js @@ -1,9 +1,11 @@ import AlertMessage from './AlertMessage.vue' -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' -/* eslint-env mocha */ describe('AlertMessage', () => { - beforeEach(mountCallback(AlertMessage)) + beforeEach(() => { + mount(AlertMessage) + }) + it('loads', () => { cy.get('button').should('be.visible') }) diff --git a/npm/vue/cypress/component/basic/code-coverage/Calculator.cy.js b/npm/vue/cypress/component/basic/code-coverage/Calculator.cy.js index 47abd16f33f1..8e3f1b5917e0 100644 --- a/npm/vue/cypress/component/basic/code-coverage/Calculator.cy.js +++ b/npm/vue/cypress/component/basic/code-coverage/Calculator.cy.js @@ -2,7 +2,8 @@ import Calculator from './Calculator.vue' import { mount } from '@cypress/vue' -describe('Calculator', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Calculator', () => { it('adds two numbers', () => { cy.viewport(400, 200) mount(Calculator) diff --git a/npm/vue/cypress/component/basic/code-coverage/unit.cy.js b/npm/vue/cypress/component/basic/code-coverage/unit.cy.js index f5ede905a9e7..bac5bb2722e6 100644 --- a/npm/vue/cypress/component/basic/code-coverage/unit.cy.js +++ b/npm/vue/cypress/component/basic/code-coverage/unit.cy.js @@ -1,7 +1,8 @@ /// import { add } from './calc' -describe('Code coverage', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Code coverage', () => { it('has code coverage object', () => { // there is an object created by Istanbul plugin cy.wrap(window) diff --git a/npm/vue/cypress/component/basic/components/README.md b/npm/vue/cypress/component/basic/components/README.md index d5025cd2d2ae..2bdb92b182bb 100644 --- a/npm/vue/cypress/component/basic/components/README.md +++ b/npm/vue/cypress/component/basic/components/README.md @@ -1,6 +1,6 @@ # global components -During mounting, you can register other components, even fake ones. See [spec.js](spec.js) +During mounting, you can register other components, even fake ones. See [spec.cy.js](spec.cy.js) ```js import MessageList from '../MessageList.vue' diff --git a/npm/vue/cypress/component/basic/components/spec.js b/npm/vue/cypress/component/basic/components/spec.cy.js similarity index 77% rename from npm/vue/cypress/component/basic/components/spec.js rename to npm/vue/cypress/component/basic/components/spec.cy.js index fe158af869bd..9b76080b0b34 100644 --- a/npm/vue/cypress/component/basic/components/spec.js +++ b/npm/vue/cypress/component/basic/components/spec.cy.js @@ -1,11 +1,12 @@ /// import MessageList from '../MessageList.vue' -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' // common utils for MessageList const getItems = () => cy.get('ul li') -describe('Global components', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Global components', () => { // two different components, each gets "numbers" list // into its property "messages" const template = ` @@ -26,7 +27,9 @@ describe('Global components', () => { components, } - beforeEach(mountCallback({ template, data }, { extensions })) + beforeEach(() => { + mount({ template, data }, { extensions }) + }) it('shows two items at the start in both lists', () => { getItems().should('have.length', 4) diff --git a/npm/vue/cypress/component/basic/list.cy.js b/npm/vue/cypress/component/basic/list.cy.js index 1ec2f408c80b..5fa406ca7d6b 100644 --- a/npm/vue/cypress/component/basic/list.cy.js +++ b/npm/vue/cypress/component/basic/list.cy.js @@ -1,7 +1,7 @@ -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' -/* eslint-env mocha */ -describe('Declarative rendering', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Declarative rendering', () => { // List example from https://vuejs.org/v2/guide/#Declarative-Rendering const template = `
      @@ -21,7 +21,9 @@ describe('Declarative rendering', () => { } } - beforeEach(mountCallback({ template, data })) + beforeEach(() => { + mount({ template, data }) + }) it('shows 3 items', () => { cy.get('li').should('have.length', 3) diff --git a/npm/vue/cypress/component/basic/mixins/README.md b/npm/vue/cypress/component/basic/mixins/README.md index 0be7e041dfa4..431fd2ec0cf9 100644 --- a/npm/vue/cypress/component/basic/mixins/README.md +++ b/npm/vue/cypress/component/basic/mixins/README.md @@ -1,5 +1,5 @@ # mixins -You can register global mixin component instances when mounting the a component. See [spec.js](spec.js) +You can register global mixin component instances when mounting the a component. See [spec.cy.js](spec.cy.js) See [Vue global mixin docs](https://vuejs.org/v2/guide/mixins.html#Global-Mixin) diff --git a/npm/vue/cypress/component/basic/mixins/spec.js b/npm/vue/cypress/component/basic/mixins/spec.cy.js similarity index 77% rename from npm/vue/cypress/component/basic/mixins/spec.js rename to npm/vue/cypress/component/basic/mixins/spec.cy.js index ea8d804d260e..772aa737261c 100644 --- a/npm/vue/cypress/component/basic/mixins/spec.js +++ b/npm/vue/cypress/component/basic/mixins/spec.cy.js @@ -1,5 +1,5 @@ /// -import { mount, mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' describe('Mixins', () => { const template = '
      mixin test
      ' @@ -17,13 +17,13 @@ describe('Mixins', () => { mixin, } - beforeEach(mountCallback({ template }, { extensions })) - it('calls mixin "created" method', () => { - // the "created" will be called twice - // 1 - when the test wrapper element made by the Vue test utils is created - // 2 - when the element above we are testing is created - expect(MyMixin.created).to.have.been.calledTwice + mount({ template }, { extensions }).then(() => { + // the "created" will be called twice + // 1 - when the test wrapper element made by the Vue test utils is created + // 2 - when the element above we are testing is created + expect(MyMixin.created).to.have.been.calledTwice + }) }) }) diff --git a/npm/vue/cypress/component/basic/options.cy.js b/npm/vue/cypress/component/basic/options.cy.js index dd8817cb95a0..ea643c328207 100644 --- a/npm/vue/cypress/component/basic/options.cy.js +++ b/npm/vue/cypress/component/basic/options.cy.js @@ -1,4 +1,4 @@ -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' const template = `
      @@ -6,7 +6,8 @@ const template = `
      ` -describe('Mount component', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Mount component', () => { // hmm, there are no more options to pass const component = { @@ -18,7 +19,9 @@ describe('Mount component', () => { }, } - beforeEach(mountCallback(component)) + beforeEach(() => { + mount(component) + }) it('shows hello', () => { cy.contains('Hello Vue!') diff --git a/npm/vue/cypress/component/basic/plugins/plugin.cy.js b/npm/vue/cypress/component/basic/plugins/plugin.cy.js index 276c5f3cf7cd..e0fc4059704e 100644 --- a/npm/vue/cypress/component/basic/plugins/plugin.cy.js +++ b/npm/vue/cypress/component/basic/plugins/plugin.cy.js @@ -1,7 +1,7 @@ /// import { MyPlugin } from './MyPlugin' import { MyPluginWithOptions } from './MyPluginWithOptions' -import { mount, mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' const EmptyComponent = { template: '
      ' } @@ -28,8 +28,9 @@ describe('Custom plugin MyPlugin', () => { use, } - // use "mountCallback" to register the plugins - beforeEach(mountCallback(EmptyComponent, { extensions })) + beforeEach(() => { + mount(EmptyComponent, { extensions }) + }) it('registers global method on Vue instance', () => { cy.wrap(Cypress).its('vue').its('aPluginMethod').should('be.a', 'function') diff --git a/npm/vue/cypress/component/basic/props/message-list.cy.ts b/npm/vue/cypress/component/basic/props/message-list.cy.ts index 2f308abcfa4e..345c20bd2352 100644 --- a/npm/vue/cypress/component/basic/props/message-list.cy.ts +++ b/npm/vue/cypress/component/basic/props/message-list.cy.ts @@ -1,6 +1,6 @@ /// import MessageList from '../MessageList.vue' -import { mount, mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' // common utils for MessageList const getItems = () => cy.get('ul li') @@ -21,7 +21,9 @@ describe('Props', () => { }) context('MessageList without props', () => { - beforeEach(mountCallback(MessageList)) + beforeEach(() => { + mount(MessageList) + }) it('shows no messages', () => { getItems().should('not.exist') @@ -38,12 +40,13 @@ describe('Props', () => { }) }) - context('MessageList with props', () => { + // TODO: fix with https://github.com/cypress-io/cypress/issues/30706 + context.skip('MessageList with props', () => { const template = ` -
      - -
      - ` +
      + +
      + ` const data = () => ({ messages: ['uno', 'dos'] }) @@ -51,7 +54,9 @@ describe('Props', () => { MessageList, } - beforeEach(mountCallback({ template, data, components })) + beforeEach(() => { + mount({ template, data, components }) + }) it('shows two items at the start', () => { getItems().should('have.length', 2) @@ -60,10 +65,10 @@ describe('Props', () => { context('MessageList under message-list name', () => { const template = ` -
      - -
      - ` +
      + +
      + ` const data = () => ({ messages: ['uno', 'dos'] }) @@ -71,13 +76,16 @@ describe('Props', () => { 'message-list': MessageList, } - beforeEach(mountCallback({ template, data, components })) + beforeEach(() => { + mount({ template, data, components }) + }) it('starts with two items', () => { expect(Cypress.vue.messages).to.deep.equal(['uno', 'dos']) }) - it('shows two items at the start', () => { + // TODO: fix with https://github.com/cypress-io/cypress/issues/30706 + it.skip('shows two items at the start', () => { getItems().should('have.length', 2) }) }) diff --git a/npm/vue/cypress/component/basic/reverse.cy.js b/npm/vue/cypress/component/basic/reverse.cy.js index 8b3ac071fe22..a866dda8e9c8 100644 --- a/npm/vue/cypress/component/basic/reverse.cy.js +++ b/npm/vue/cypress/component/basic/reverse.cy.js @@ -1,7 +1,7 @@ -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' -/* eslint-env mocha */ -describe('Handling User Input', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Handling User Input', () => { // Example from https://vuejs.org/v2/guide/#Handling-User-Input const template = `
      @@ -20,7 +20,9 @@ describe('Handling User Input', () => { }, } - beforeEach(mountCallback({ template, data, methods })) + beforeEach(() => { + mount({ template, data, methods }) + }) it('reverses text', () => { cy.contains('Hello Vue') diff --git a/npm/vue/cypress/component/basic/spec.js b/npm/vue/cypress/component/basic/spec.cy.js similarity index 71% rename from npm/vue/cypress/component/basic/spec.js rename to npm/vue/cypress/component/basic/spec.cy.js index 777caccf6601..e339888a44ab 100644 --- a/npm/vue/cypress/component/basic/spec.js +++ b/npm/vue/cypress/component/basic/spec.cy.js @@ -1,7 +1,7 @@ -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' -/* eslint-env mocha */ -describe('Declarative rendering', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Declarative rendering', () => { // Vue code from https://vuejs.org/v2/guide/#Declarative-Rendering const template = `
      @@ -9,14 +9,14 @@ describe('Declarative rendering', () => {
      ` - beforeEach( - mountCallback({ + beforeEach(() => { + mount({ template, data () { return { message: 'Hello Vue!' } }, - }), - ) + }) + }) it('shows hello', () => { cy.contains('Hello Vue!') diff --git a/npm/vue/cypress/component/basic/style-in-spec/style.css b/npm/vue/cypress/component/basic/style-in-spec/style.css new file mode 100644 index 000000000000..31339d31d2ab --- /dev/null +++ b/npm/vue/cypress/component/basic/style-in-spec/style.css @@ -0,0 +1,4 @@ +.todo.done { + text-decoration: line-through; + color: gray; +} diff --git a/npm/vue/cypress/component/basic/style-in-spec/todo.cy.js b/npm/vue/cypress/component/basic/style-in-spec/todo.cy.js index 424bd869aca0..0422d351b4b5 100644 --- a/npm/vue/cypress/component/basic/style-in-spec/todo.cy.js +++ b/npm/vue/cypress/component/basic/style-in-spec/todo.cy.js @@ -1,15 +1,9 @@ import Todo from './Todo.vue' import { mount } from '@cypress/vue' +import './style.css' -// let's make sure we can show the checked Todo item using CSS -const style = ` - .todo.done { - text-decoration: line-through; - color: gray; - } -` - -it('injects local style', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +it.skip('imports and applies style', () => { // see https://vuejs.org/v2/guide/components-props.html const template = ` @@ -18,7 +12,7 @@ it('injects local style', () => { Todo, } - mount({ template, components }, { style }) + mount({ template, components }) cy.get('input[type=checkbox]') .should('not.be.checked') .check() @@ -36,14 +30,12 @@ it('injects local style', () => { }) it('passes props via options object', () => { - // if you want to pass props right away, without creating a template - // use "props" key + // if you want to pass props right away, without creating a template use "props" key const options = { props: { title: 'finish test', done: true, }, - style, } mount(Todo, options) diff --git a/npm/vue/cypress/component/button/button-counter.cy.js b/npm/vue/cypress/component/button/button-counter.cy.js index 809197bb48f6..077aa80a2c93 100644 --- a/npm/vue/cypress/component/button/button-counter.cy.js +++ b/npm/vue/cypress/component/button/button-counter.cy.js @@ -1,9 +1,11 @@ import ButtonCounter from './ButtonCounter.vue' -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' /* eslint-env mocha */ describe('ButtonCounter', () => { - beforeEach(mountCallback(ButtonCounter)) + beforeEach(() => { + mount(ButtonCounter) + }) it('starts with zero', () => { cy.contains('button', '0') diff --git a/npm/vue/cypress/component/counter-vuex/counter-vuex.cy.js b/npm/vue/cypress/component/counter-vuex/counter-vuex.cy.js index 56e16dcde857..a763a31727fb 100644 --- a/npm/vue/cypress/component/counter-vuex/counter-vuex.cy.js +++ b/npm/vue/cypress/component/counter-vuex/counter-vuex.cy.js @@ -2,10 +2,10 @@ // https://github.com/vuejs/vuex/tree/dev/examples/counter import Counter from './Counter.vue' import store from './store' -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' -/* eslint-env mocha */ -describe('Vuex Counter', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Vuex Counter', () => { // configure component const extensions = { plugins: [store], @@ -22,7 +22,9 @@ describe('Vuex Counter', () => { const setCount = (value) => Cypress.vue.$store.commit('set', value) // initialize a fresh Vue app before each test - beforeEach(mountCallback({ template, store }, { extensions })) + beforeEach(() => { + mount({ template, store }, { extensions }) + }) it('starts with zero', () => { cy.contains('0 times') diff --git a/npm/vue/cypress/component/hello/hello-component.cy.js b/npm/vue/cypress/component/hello/hello-component.cy.js index dd0735c45218..e512e3651045 100644 --- a/npm/vue/cypress/component/hello/hello-component.cy.js +++ b/npm/vue/cypress/component/hello/hello-component.cy.js @@ -1,16 +1,15 @@ import Hello from './Hello.vue' -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' -/* eslint-env mocha */ describe('Hello.vue', () => { - beforeEach(mountCallback(Hello)) - it('shows hello', () => { + mount(Hello) cy.contains('Hello World!') }) }) -describe('Several components', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Several components', () => { const template = `
      @@ -22,9 +21,8 @@ describe('Several components', () => { hello: Hello, } - beforeEach(mountCallback({ template, components })) - it('greets the world 3 times', () => { + mount({ template, components }) cy.get('p').should('have.length', 3) }) }) diff --git a/npm/vue/cypress/component/removedMountingOptions.cy.js b/npm/vue/cypress/component/removedMountingOptions.cy.js deleted file mode 100644 index 3e093ba042fd..000000000000 --- a/npm/vue/cypress/component/removedMountingOptions.cy.js +++ /dev/null @@ -1,22 +0,0 @@ -import { mount } from '@cypress/vue' -import { h, defineComponent } from 'vue' - -describe('removed mounting options', () => { - it('throws error when receiving removed mounting options', () => { - const comp = defineComponent({ - setup () { - return () => h('div', 'hello world') - }, - }) - - for (const key of ['cssFile', 'cssFiles', 'style', 'styles', 'stylesheet', 'stylesheets']) { - expect(() => { - return mount(comp, { - [key]: `body { background: red; }`, - }) - }).to.throw( - `The \`${key}\` mounting option is no longer supported.`, - ) - } - }) -}) diff --git a/npm/vue/cypress/component/router-example/router.cy.js b/npm/vue/cypress/component/router-example/router.cy.js index db8ce6daec3d..006bf594758e 100644 --- a/npm/vue/cypress/component/router-example/router.cy.js +++ b/npm/vue/cypress/component/router-example/router.cy.js @@ -1,8 +1,9 @@ import PizzaShop from './PizzaShop/index.vue' import router from './PizzaShop/router' -import { mountCallback } from '@cypress/vue' +import { mount } from '@cypress/vue' -describe('Vue Router - Pizza Shop', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('Vue Router - Pizza Shop', () => { // configure component const extensions = { plugins: [router], @@ -15,7 +16,9 @@ describe('Vue Router - Pizza Shop', () => { const template = '' // initialize a fresh Vue app before each test - beforeEach(mountCallback({ template, router }, { extensions })) + beforeEach(() => { + mount({ template, router }, { extensions }) + }) it('go to order page', () => { cy.get('button.order').click() diff --git a/npm/vue/cypress/component/tailwind/plain-html.cy.js b/npm/vue/cypress/component/tailwind/plain-html.cy.js index c489d137df98..f8be7146ec28 100644 --- a/npm/vue/cypress/component/tailwind/plain-html.cy.js +++ b/npm/vue/cypress/component/tailwind/plain-html.cy.js @@ -1,4 +1,5 @@ import { mount } from '@cypress/vue' +import '/node_modules/tailwindcss/dist/tailwind.min.css' // example comes from https://tailwindcss.com/components/cards/#horizontal const html = ` @@ -27,15 +28,13 @@ const html = `
      ` -it('renders card', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +it.skip('renders card', () => { cy.viewport(1000, 500) mount( { template: html, }, - { - stylesheets: '/node_modules/tailwindcss/dist/tailwind.min.css', - }, ) cy.contains('.text-xl', 'Can coffee make you a better developer?') diff --git a/npm/vue/cypress/component/tailwind/redbox.css b/npm/vue/cypress/component/tailwind/redbox.css new file mode 100644 index 000000000000..98909415202a --- /dev/null +++ b/npm/vue/cypress/component/tailwind/redbox.css @@ -0,0 +1,3 @@ +@import 'https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css'; + +body { background: blue; } diff --git a/npm/vue/cypress/component/tailwind/redbox.cy.js b/npm/vue/cypress/component/tailwind/redbox.cy.js index 01a5041a0dde..992e656a06c0 100644 --- a/npm/vue/cypress/component/tailwind/redbox.cy.js +++ b/npm/vue/cypress/component/tailwind/redbox.cy.js @@ -1,11 +1,11 @@ import { mount } from '@cypress/vue' import RedBox from './RedBox.vue' - -const tailwindCdnLink = 'https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css' +import './redbox.css' const inlineStyle = 'body { background: blue; }' -describe('RedBox 1', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('RedBox 1', () => { const template = '' const options = { extensions: { @@ -13,13 +13,6 @@ describe('RedBox 1', () => { 'red-box': RedBox, }, }, - // you can inject additional styles to be downloaded - // - style: inlineStyle, - stylesheets: [ - // you can use external links - tailwindCdnLink, - ], } it('displays red Hello RedBox', () => { @@ -35,7 +28,8 @@ describe('RedBox 1', () => { }) }) -describe('RedBox 2', () => { +// TODO: fix with https://github.com/cypress-io/cypress/issues/30706 +describe.skip('RedBox 2', () => { const template = '' const options = { extensions: { @@ -43,10 +37,6 @@ describe('RedBox 2', () => { 'red-box': RedBox, }, }, - stylesheets: [ - // you can use external links - tailwindCdnLink, - ], } beforeEach(() => { diff --git a/npm/vue/cypress/component/test-utils-api/TestUtilsApi.cy.ts b/npm/vue/cypress/component/test-utils-api/TestUtilsApi.cy.ts index 0ff8252c5d22..ee6af88bd7f5 100644 --- a/npm/vue/cypress/component/test-utils-api/TestUtilsApi.cy.ts +++ b/npm/vue/cypress/component/test-utils-api/TestUtilsApi.cy.ts @@ -28,13 +28,4 @@ describe('VueTestUtils API', () => { expect(component.$data.foo).to.eq('bar') }) }) - - it('errors when attempting to access Vue Wrapper without destructuring', () => { - mount(TestUtilsApi, { props: { msg: 'Hello world!' } }).then((vueWrapper) => { - // @ts-expect-error - these are removed - expect(() => vueWrapper.vm).to.throw('As of Cypress 11, mount now yields an object with VueWrapper as a property. Destructure using `{ wrapper }` to access the VueWrapper.') - // @ts-expect-error - these are removed - expect(() => vueWrapper.find('h2').text()).to.throw('As of Cypress 11, mount now yields an object with VueWrapper as a property. Destructure using `{ wrapper }` to access the VueWrapper.') - }) - }) }) diff --git a/npm/vue/package.json b/npm/vue/package.json index e2d3118b4731..bb4da793467a 100644 --- a/npm/vue/package.json +++ b/npm/vue/package.json @@ -18,7 +18,7 @@ "@cypress/mount-utils": "0.0.0-development", "@vitejs/plugin-vue": "5.0.4", "@vue/compiler-sfc": "3.2.47", - "@vue/test-utils": "2.3.2", + "@vue/test-utils": "2.4.6", "axios": "0.21.2", "cypress": "0.0.0-development", "debug": "^4.3.4", @@ -42,7 +42,7 @@ "src/**/*.js" ], "engines": { - "node": ">=8" + "node": ">=18" }, "types": "dist/index.d.ts", "license": "MIT", @@ -82,9 +82,6 @@ "nx": { "targets": { "build": { - "dependsOn": [ - "!@cypress/react18:build" - ], "outputs": [ "{workspaceRoot}/cli/vue", "{projectRoot}/dist" diff --git a/npm/vue/src/index.ts b/npm/vue/src/index.ts index 92d60b73bc29..b3826b409b69 100644 --- a/npm/vue/src/index.ts +++ b/npm/vue/src/index.ts @@ -23,7 +23,6 @@ import type { MountingOptions as VTUMountingOptions, VueWrapper } from '@vue/tes import { getContainerEl, setupHooks, - checkForRemovedStyleOptions, } from '@cypress/mount-utils' import * as _VueTestUtils from '@vue/test-utils' @@ -393,7 +392,6 @@ export function mount< * }) */ export function mount (componentOptions: any, options: any = {}) { - checkForRemovedStyleOptions(options) // Remove last mounted component if cy.mount is called more than once in a test cleanup() @@ -441,17 +439,7 @@ export function mount (componentOptions: any, options: any = {}) { component: wrapper.vm, } - return new Proxy(Object.create(returnVal), { - get (obj, prop) { - // throw an error if it looks like the caller is trying to call a method on the VueWrapper that was originally returned - if (Reflect.get(wrapper, prop)) { - // @ts-expect-error - internal API - Cypress.utils.throwErrByPath('mount.vue_yielded_value') - } - - return Reflect.get(obj, prop) - }, - }) + return returnVal }) }) } @@ -480,25 +468,6 @@ function getComponentDisplayName (componentOptions: any): string { return DEFAULT_COMP_NAME } -/** - * Helper function for mounting a component quickly in test hooks. - * @example - * import {mountCallback} from '@cypress/vue' - * beforeEach(mountVue(component, options)) - * - * Removed as of Cypress 11.0.0. - * @see https://on.cypress.io/migration-11-0-0-component-testing-updates - */ -export function mountCallback ( - component: any, - options: any = {}, -) { - return () => { - // @ts-expect-error - undocumented API - Cypress.utils.throwErrByPath('mount.mount_callback') - } -} - // Side effects from "import { mount } from '@cypress/'" are annoying, we should avoid doing this // by creating an explicit function/import that the user can register in their 'component.js' support file, // such as: diff --git a/npm/vue2/.eslintignore b/npm/vue2/.eslintignore deleted file mode 100644 index 79afe972da7d..000000000000 --- a/npm/vue2/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -**/dist -**/*.d.ts -**/package-lock.json -**/tsconfig.json -**/cypress/fixtures \ No newline at end of file diff --git a/npm/vue2/.eslintrc b/npm/vue2/.eslintrc deleted file mode 100644 index f191aaa0e6d3..000000000000 --- a/npm/vue2/.eslintrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "plugins": [ - "cypress" - ], - "extends": [ - "plugin:@cypress/dev/tests" - ], - "env": { - "cypress/globals": true - }, - "rules": { - "mocha/no-global-tests": "off", - "no-console": "off" - } -} diff --git a/npm/vue2/.npmrc b/npm/vue2/.npmrc deleted file mode 100644 index ff1f40faed0b..000000000000 --- a/npm/vue2/.npmrc +++ /dev/null @@ -1,3 +0,0 @@ -save-exact=true -progress=false -package-lock=true diff --git a/npm/vue2/.releaserc.js b/npm/vue2/.releaserc.js deleted file mode 100644 index 17d3bb871472..000000000000 --- a/npm/vue2/.releaserc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - ...require('../../.releaserc'), -} diff --git a/npm/vue2/CHANGELOG.md b/npm/vue2/CHANGELOG.md deleted file mode 100644 index 29b1f52a8007..000000000000 --- a/npm/vue2/CHANGELOG.md +++ /dev/null @@ -1,107 +0,0 @@ -# [@cypress/vue2-v2.1.1](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v2.1.0...@cypress/vue2-v2.1.1) (2024-06-07) - - -### Bug Fixes - -* update cypress to Typescript 5 ([#29568](https://github.com/cypress-io/cypress/issues/29568)) ([f3b6766](https://github.com/cypress-io/cypress/commit/f3b67666a5db0438594339c379cf27e1fd1e4abc)) - -# [@cypress/vue2-v2.1.0](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v2.0.1...@cypress/vue2-v2.1.0) (2024-03-12) - - -### Features - -* supported type of vue@2.7+ ([#28818](https://github.com/cypress-io/cypress/issues/28818)) ([854a649](https://github.com/cypress-io/cypress/commit/854a6497be2315881b8ad9c92674d3c29a76d581)) - -# [@cypress/vue2-v2.0.1](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v2.0.0...@cypress/vue2-v2.0.1) (2022-11-14) - - -### Bug Fixes - -* vue2 global directives in component testing ([#24488](https://github.com/cypress-io/cypress/issues/24488)) ([741019d](https://github.com/cypress-io/cypress/commit/741019d9618b7be79db64c9039ebca07741dd5c7)) - -# [@cypress/vue2-v2.0.0](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v1.1.2...@cypress/vue2-v2.0.0) (2022-11-07) - - -### Bug Fixes - -* remove dependence on @cypress/ types ([#24415](https://github.com/cypress-io/cypress/issues/24415)) ([58e0ab9](https://github.com/cypress-io/cypress/commit/58e0ab91604618ea6f75932622f7e66e419270e6)) -* remove last mounted component upon subsequent mount calls ([#24470](https://github.com/cypress-io/cypress/issues/24470)) ([f39eb1c](https://github.com/cypress-io/cypress/commit/f39eb1c19e0923bda7ae263168fc6448da942d54)) -* remove some CT functions and props ([#24419](https://github.com/cypress-io/cypress/issues/24419)) ([294985f](https://github.com/cypress-io/cypress/commit/294985f8b3e0fa00ed66d25f88c8814603766074)) - - -### Features - -* include component and wrapper in return type for vue mount adapter ([#24479](https://github.com/cypress-io/cypress/issues/24479)) ([33875d7](https://github.com/cypress-io/cypress/commit/33875d75505416b1f65ca7c6d5dedc46f3289f1b)) - - -### BREAKING CHANGES - -* remove last mounted component upon subsequent mount calls of mount -* Vue mount returns wrapper and component rather than wrapper only - -# [@cypress/vue2-v1.1.2](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v1.1.1...@cypress/vue2-v1.1.2) (2022-11-01) - - -### Bug Fixes - -* Hovering over mount in command log does not show component in AUT ([#24346](https://github.com/cypress-io/cypress/issues/24346)) ([355d210](https://github.com/cypress-io/cypress/commit/355d2101d38ea4d1e93b9c571cf77babab2bbbfc)) - -# [@cypress/vue2-v1.1.1](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v1.1.0...@cypress/vue2-v1.1.1) (2022-10-13) - - -### Bug Fixes - -* angular and nuxt ct tests now fail on uncaught exceptions ([#24122](https://github.com/cypress-io/cypress/issues/24122)) ([53eef4f](https://github.com/cypress-io/cypress/commit/53eef4fbd7e1caf32f0183cadbc0e4cf05524c34)) - - -# [@cypress/vue2-v1.1.0](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v1.0.2...@cypress/vue2-v1.1.0) (2022-08-30) - - -### Features - -* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8)) - -# [@cypress/vue2-v1.0.2](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v1.0.1...@cypress/vue2-v1.0.2) (2022-08-11) - - -### Bug Fixes - -* remove CT side effects from mount when e2e testing ([#22633](https://github.com/cypress-io/cypress/issues/22633)) ([a9476ec](https://github.com/cypress-io/cypress/commit/a9476ecb3d43f628b689e060294a1952937cb1a7)) - -# [@cypress/vue2-v1.0.1](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v1.0.0...@cypress/vue2-v1.0.1) (2022-06-13) - - -### Bug Fixes - -* remove http npm registry link for vue2 ([0bd3069](https://github.com/cypress-io/cypress/commit/0bd306962bce2a32d7b87fc1811a7b9feeb63ae2)) - -# @cypress/vue2-v1.0.0 (2022-06-13) - - -### Bug Fixes - -* add package.json metadata for webpack-dev-server ([#22292](https://github.com/cypress-io/cypress/issues/22292)) ([9cfec97](https://github.com/cypress-io/cypress/commit/9cfec9750f2ddc9fe691aabbe2ecc9bc02a3d915)) -* display cy.mount command log ([#21500](https://github.com/cypress-io/cypress/issues/21500)) ([140b4ba](https://github.com/cypress-io/cypress/commit/140b4ba2110243712a614a39b2408c30cce4d0b1)) -* Doc changes around vue2 ([#21066](https://github.com/cypress-io/cypress/issues/21066)) ([17905a7](https://github.com/cypress-io/cypress/commit/17905a79ee5106b0d72c8e74bb717fcd7b796dee)) - - -### chore - -* prep npm packages for use with Cypress v10 ([b924d08](https://github.com/cypress-io/cypress/commit/b924d086ee2e2ccc93303731e001b2c9e9d0af17)) - - -### Features - -* Add vue2 package from npm/vue/v2 branch ([#21026](https://github.com/cypress-io/cypress/issues/21026)) ([3aa69e2](https://github.com/cypress-io/cypress/commit/3aa69e2538aae5702bfc48789c54f37263ce08fc)) -* swap the #__cy_root id selector to become data-cy-root for component mounting ([#20951](https://github.com/cypress-io/cypress/issues/20951)) ([0e7b555](https://github.com/cypress-io/cypress/commit/0e7b555f93fb403f431c5de4a07ae7ad6ac89ba2)) - - -### BREAKING CHANGES - -* new version of packages for Cypress v10 - -# @cypress/vue2-v1.0.0 (2021-06-17) - -### Features - -* Split out as separate package from `@cypress/vue`, based on the `npm/vue/v2` branch. diff --git a/npm/vue2/README.md b/npm/vue2/README.md deleted file mode 100644 index 0dcb604eebaf..000000000000 --- a/npm/vue2/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# @cypress/vue2 - -Mount Vue 2 components in the open source [Cypress.io](https://www.cypress.io/) test runner - -> **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [Vue Component Testing Docs](https://docs.cypress.io/guides/component-testing/vue/overview) for mounting Vue components. Installing and importing `mount` from `@cypress/vue2` should only be done for advanced use-cases. - -## [Changelog](./CHANGELOG.md) diff --git a/npm/vue2/babel.config.json b/npm/vue2/babel.config.json deleted file mode 100644 index 589f7ada6cbd..000000000000 --- a/npm/vue2/babel.config.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "plugins": [ - "@babel/plugin-transform-modules-commonjs", - [ - "babel-plugin-istanbul", - { - "extension": [ - ".js", - ".vue" - ] - } - ] - ] -} diff --git a/npm/vue2/docs/styles.md b/npm/vue2/docs/styles.md deleted file mode 100644 index d6b0542f9228..000000000000 --- a/npm/vue2/docs/styles.md +++ /dev/null @@ -1,47 +0,0 @@ -# styles - -If your component imports its own style, the style should be applied during the Cypress test. - -```js -// MyComponent.vue - - - -``` - -You can also load styles in the following ways: - -## Import in the spec file - -```js -import '../styles/main.css' - -const myComponent = { - template: '' -} - -mount(myComponent) -``` - -## Import in the component support file - -If you have stylesheets that should apply to all of your components, you can import those from your component support file. - -```js -// cypress/support/component.js -import '../styles/main.css' -... - -// MyComponent.spec.js -const myComponent = { - template: '' -} - -mount(myComponent) -``` diff --git a/npm/vue2/package.json b/npm/vue2/package.json deleted file mode 100644 index e71a42d2f880..000000000000 --- a/npm/vue2/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "@cypress/vue2", - "version": "0.0.0-development", - "description": "Browser-based Component Testing for Vue.js@2 with Cypress.io ✌️🌲", - "main": "dist/cypress-vue2.cjs.js", - "scripts": { - "build": "rimraf dist && yarn rollup -c rollup.config.mjs", - "postbuild": "node ../../scripts/sync-exported-npm-with-cli.js", - "check-ts": "tsc --noEmit", - "lint": "eslint --ext .js,.jsx,.ts,.tsx,.json,.vue .", - "test": "echo \"Tests for @cypress/vue2 are run from system-tests\"", - "test-ci": "node ../../scripts/run-ct-examples.js --examplesList=./examples.env", - "watch": "yarn build --watch --watch.exclude ./dist/**/*" - }, - "devDependencies": { - "@cypress/mount-utils": "0.0.0-development", - "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-replace": "^2.3.1", - "@vue/test-utils": "^1.3.1", - "tslib": "^2.1.0", - "typescript": "~5.4.5", - "vue": "2.7.16" - }, - "peerDependencies": { - "cypress": ">=4.5.0", - "vue": "^2.0.0" - }, - "files": [ - "dist/**/*", - "src/**/*.js" - ], - "engines": { - "node": ">=8" - }, - "types": "dist/index.d.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/cypress-io/cypress.git" - }, - "homepage": "https://github.com/cypress-io/cypress/blob/develop/npm/vue/#readme", - "bugs": "https://github.com/cypress-io/cypress/issues/new?assignees=&labels=npm%3A%20%40cypress%2Fvue&template=1-bug-report.md&title=", - "keywords": [ - "cypress", - "vue" - ], - "unpkg": "dist/cypress-vue2.browser.js", - "module": "dist/cypress-vue2.esm-bundler.js", - "publishConfig": { - "access": "public" - }, - "nx": { - "targets": { - "build": { - "outputs": [ - "{workspaceRoot}/cli/vue2", - "{projectRoot}/dist" - ] - } - }, - "implicitDependencies": [ - "!cypress" - ] - } -} diff --git a/npm/vue2/rollup.config.mjs b/npm/vue2/rollup.config.mjs deleted file mode 100644 index b693c9343a49..000000000000 --- a/npm/vue2/rollup.config.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import { createEntries } from '@cypress/mount-utils/create-rollup-entry.mjs' -import json from '@rollup/plugin-json' -import replace from '@rollup/plugin-replace' - -const dtsOptions = { - respectExternal: false, -} - -const config = { - external: [ - 'vue', - ], - plugins: [ - json(), - /** - * Vue 2 core tries to load require.resolve(`./package.json`) in the browser for the - * sole purpose of throwing an error about Vue Loader. - * Just truncate this for now for simplicity. - */ - replace({ - 'vueVersion && vueVersion !== packageVersion': JSON.stringify(false), - preventAssignment: false, - }), - ], - output: { - globals: { - vue: 'Vue', - }, - }, -} - -export default createEntries({ formats: ['es', 'cjs'], input: 'src/index.ts', config, dtsOptions }) diff --git a/npm/vue2/src/index.ts b/npm/vue2/src/index.ts deleted file mode 100644 index c5c7a6c5541a..000000000000 --- a/npm/vue2/src/index.ts +++ /dev/null @@ -1,478 +0,0 @@ -/// -import Vue from 'vue' -import { - createLocalVue, - mount as testUtilsMount, - VueTestUtilsConfigOptions, - Wrapper, -} from '@vue/test-utils' -import { - getContainerEl, - setupHooks, - checkForRemovedStyleOptions, -} from '@cypress/mount-utils' -import { ComponentPublicInstanceConstructor } from 'vue/types/v3-component-public-instance' - -const defaultOptions: (keyof MountOptions)[] = [ - 'vue', - 'extensions', -] - -const DEFAULT_COMP_NAME = 'unknown' - -const registerGlobalComponents = (Vue, options) => { - const globalComponents = Cypress._.get(options, 'extensions.components') - - if (Cypress._.isPlainObject(globalComponents)) { - Cypress._.forEach(globalComponents, (component, id) => { - Vue.component(id, component) - }) - } -} - -const installFilters = (Vue, options) => { - const filters: VueFilters | undefined = Cypress._.get( - options, - 'extensions.filters', - ) - - if (Cypress._.isPlainObject(filters)) { - Object.keys(filters).forEach((name) => { - Vue.filter(name, filters[name]) - }) - } -} - -const installPlugins = (Vue, options, props) => { - const plugins: VuePlugins = - Cypress._.get(props, 'plugins') || - Cypress._.get(options, 'extensions.use') || - Cypress._.get(options, 'extensions.plugins') || - [] - - // @ts-ignore - plugins.forEach((p) => { - Array.isArray(p) ? Vue.use(...p) : Vue.use(p) - }) -} - -const installMixins = (Vue, options) => { - const mixins = - Cypress._.get(options, 'extensions.mixin') || - Cypress._.get(options, 'extensions.mixins') - - if (Cypress._.isArray(mixins)) { - mixins.forEach((mixin) => { - Vue.mixin(mixin) - }) - } -} - -const registerGlobalDirectives = (Vue, options) => { - const directives = - Cypress._.get(options, 'extensions.directives') - - if (Cypress._.isPlainObject(directives)) { - Object.keys(directives).forEach((name) => { - Vue.directive(name, directives[name]) - }) - } -} - -const hasStore = ({ store }: { store: any }) => Boolean(store && store._vm) - -const forEachValue = (obj: Record, fn: (value: T, key: string) => void) => { - return Object.keys(obj).forEach((key) => fn(obj[key], key)) -} - -const resetStoreVM = (Vue, { store }) => { - // bind store public getters - store.getters = {} - const wrappedGetters = store._wrappedGetters as Record void> - const computed = {} - - forEachValue(wrappedGetters, (fn, key) => { - // use computed to leverage its lazy-caching mechanism - computed[key] = () => fn(store) - Object.defineProperty(store.getters, key, { - get: () => store._vm[key], - enumerable: true, // for local getters - }) - }) - - store._watcherVM = new Vue() - store._vm = new Vue({ - data: { - $$state: store._vm._data.$$state, - }, - computed, - }) - - return store -} - -/** - * Type for component passed to "mount" - * - * @interface VueComponent - * @example - * import Hello from './Hello.vue' - * ^^^^^ this type - * mount(Hello) - */ -type VueComponent = Vue.ComponentOptions | Vue.VueConstructor | ComponentPublicInstanceConstructor - -/** - * Options to pass to the component when creating it, like - * props. - * - * @interface ComponentOptions - */ -type ComponentOptions = Record - -// local placeholder types -type VueLocalComponents = Record - -type VueFilters = { - [key: string]: (value: string) => string -} - -type VueDirectives = { - [key: string]: Function | Object -} - -type VueMixin = unknown -type VueMixins = VueMixin | VueMixin[] - -type VuePluginOptions = unknown -type VuePlugin = unknown | [unknown, VuePluginOptions] -/** - * A single Vue plugin or a list of plugins to register - */ -type VuePlugins = VuePlugin[] - -/** - * Additional Vue services to register while mounting the component, like - * local components, plugins, etc. - * - * @interface MountOptionsExtensions - * @see https://github.com/cypress-io/cypress/tree/develop/npm/vue#examples - */ -interface MountOptionsExtensions { - /** - * Extra local components - * - * @memberof MountOptionsExtensions - * @see https://github.com/cypress-io/cypress/tree/develop/npm/vue#examples - * @example - * import Hello from './Hello.vue' - * // imagine Hello needs AppComponent - * // that it uses in its template like - * // during testing we can replace it with a mock component - * const appComponent = ... - * const components = { - * 'app-component': appComponent - * }, - * mount(Hello, { extensions: { components }}) - */ - components?: VueLocalComponents - - /** - * Optional Vue filters to install while mounting the component - * - * @memberof MountOptionsExtensions - * @see https://github.com/cypress-io/cypress/tree/develop/npm/vue#examples - * @example - * const filters = { - * reverse: (s) => s.split('').reverse().join(''), - * } - * mount(Hello, { extensions: { filters }}) - */ - filters?: VueFilters - - /** - * Optional Vue mixin(s) to install when mounting the component - * - * @memberof MountOptionsExtensions - * @alias mixins - * @see https://github.com/cypress-io/cypress/tree/develop/npm/vue#examples - */ - mixin?: VueMixins - - /** - * Optional Vue mixin(s) to install when mounting the component - * - * @memberof MountOptionsExtensions - * @alias mixin - * @see https://github.com/cypress-io/cypress/tree/develop/npm/vue#examples - */ - mixins?: VueMixins - - /** - * A single plugin or multiple plugins. - * - * @see https://github.com/cypress-io/cypress/tree/develop/npm/vue#examples - * @alias plugins - * @memberof MountOptionsExtensions - */ - use?: VuePlugins - - /** - * A single plugin or multiple plugins. - * - * @see https://github.com/cypress-io/cypress/tree/develop/npm/vue#examples - * @alias use - * @memberof MountOptionsExtensions - */ - plugins?: VuePlugins - - /** - * Optional Vue directives to install while mounting the component - * - * @memberof MountOptionsExtensions - * @see https://github.com/cypress-io/cypress/tree/develop/npm/vue#examples - * @example - * const directives = { - * custom: { - * name: 'custom', - * bind (el, binding) { - * el.dataset['custom'] = binding.value - * }, - * unbind (el) { - * el.removeAttribute('data-custom') - * }, - * }, - * } - * mount(Hello, { extensions: { directives }}) - */ - directives?: VueDirectives -} - -/** - * Options controlling how the component is going to be mounted, - * including global Vue plugins and extensions. - * - * @interface MountOptions - */ -interface MountOptions { - /** - * Vue instance to use. - * - * @deprecated - * @memberof MountOptions - */ - vue: unknown - - /** - * Extra Vue plugins, mixins, local components to register while - * mounting this component - * - * @memberof MountOptions - * @see https://github.com/cypress-io/cypress/tree/develop/npm/vue#examples - */ - extensions: MountOptionsExtensions -} - -/** - * Utility type for union of options passed to "mount(..., options)" - */ -type MountOptionsArgument = Partial - -// when we mount a Vue component, we add it to the global Cypress object -// so here we extend the global Cypress namespace and its Cypress interface -declare global { - // eslint-disable-next-line no-redeclare - namespace Cypress { - interface Cypress { - /** - * Mounted Vue instance is available under Cypress.vue - * @memberof Cypress - * @example - * mount(Greeting) - * .then(() => { - * Cypress.vue.message = 'Hello There' - * }) - * // new message is displayed - * cy.contains('Hello There').should('be.visible') - */ - vue: Vue - vueWrapper: Wrapper - } - } -} - -const cleanup = () => { - Cypress.vueWrapper?.destroy() -} - -/** - * Direct Vue errors to the top error handler - * where they will fail Cypress test - * @see https://vuejs.org/v2/api/#errorHandler - * @see https://github.com/cypress-io/cypress/issues/7910 - */ -function failTestOnVueError (err, vm, info) { - // Vue 2 try catches the error-handler so push the error to be caught outside - // of the handler. - setTimeout(() => { - throw err - }) -} - -/** - * Extract the component name from the object passed to mount - * @param componentOptions the component passed to mount - * @returns name of the component - */ -function getComponentDisplayName (componentOptions: any): string { - if (componentOptions.name) { - return componentOptions.name - } - - if (componentOptions.__file) { - const filepathSplit = componentOptions.__file.split('/') - const fileName = filepathSplit[filepathSplit.length - 1] ?? DEFAULT_COMP_NAME - - // remove the extension .js, .ts or .vue from the filename to get the name of the component - const baseFileName = fileName.replace(/\.(js|ts|vue)?$/, '') - - // if the filename is index, then we can use the direct parent foldername, else use the name itself - return (baseFileName === 'index' ? filepathSplit[filepathSplit.length - 2] : baseFileName) - } - - return DEFAULT_COMP_NAME -} - -/** - * Mounts a Vue component inside Cypress browser. - * @param {VueComponent} component imported from Vue file - * @param {MountOptionsArgument} optionsOrProps used to pass options to component being mounted - * @returns {Cypress.Chainable<{wrapper: Wrapper, component: T} - * @example - * import { mount } from '@cypress/vue' - * import { Stepper } from './Stepper.vue' - * - * it('mounts', () => { - * cy.mount(Stepper) - * cy.get('[data-cy=increment]').click() - * cy.get('[data-cy=counter]').should('have.text', '1') - * }) - * @see {@link https://on.cypress.io/mounting-vue} for more details. - * - */ -export const mount = ( - component: VueComponent, - optionsOrProps: MountOptionsArgument = {}, -): Cypress.Chainable<{ - wrapper: Wrapper - component: Wrapper['vm'] -}> => { - checkForRemovedStyleOptions(optionsOrProps) - // Remove last mounted component if cy.mount is called more than once in a test - cleanup() - - const options: Partial = Cypress._.pick( - optionsOrProps, - defaultOptions, - ) - const props: Partial = Cypress._.omit( - optionsOrProps, - defaultOptions, - ) - - const componentName = getComponentDisplayName(component) - const message = `<${componentName} ... />` - - return cy - .window({ - log: false, - }) - .then((win) => { - const localVue = createLocalVue() - - // @ts-ignore - win.Vue = localVue - localVue.config.errorHandler = failTestOnVueError - - // set global Vue instance: - // 1. convenience for debugging in DevTools - // 2. some libraries might check for this global - // appIframe.contentWindow.Vue = localVue - - // refresh inner Vue instance of Vuex store - // @ts-ignore - if (hasStore(component)) { - // @ts-ignore - component.store = resetStoreVM(localVue, component) - } - - // @ts-ignore - const document: Document = cy.state('document') - - let el = getContainerEl() - - const componentNode = document.createElement('div') - - el.append(componentNode) - - // setup Vue instance - installFilters(localVue, options) - installMixins(localVue, options) - installPlugins(localVue, options, props) - registerGlobalDirectives(localVue, options) - registerGlobalComponents(localVue, options) - - props.attachTo = componentNode - - const wrapper = localVue.extend(component as any) - - const VTUWrapper = testUtilsMount(wrapper, { localVue, ...props }) - - Cypress.vue = VTUWrapper.vm - Cypress.vueWrapper = VTUWrapper - - return { - wrapper: VTUWrapper, - component: VTUWrapper.vm, - } - }) - .then(() => { - if (optionsOrProps.log !== false) { - return Vue.nextTick(() => { - Cypress.log({ - name: 'mount', - message: [message], - }) - }) - } - }) -} - -/** - * Helper function for mounting a component quickly in test hooks. - * @example - * import {mountCallback} from '@cypress/vue2' - * beforeEach(mountVue(component, options)) - * - * Removed as of Cypress 11.0.0. - * @see https://on.cypress.io/migration-11-0-0-component-testing-updates - */ -export const mountCallback = ( - component: VueComponent, - options?: MountOptionsArgument, -) => { - return () => { - // @ts-expect-error - undocumented API - Cypress.utils.throwErrByPath('mount.mount_callback') - } -} - -// Side effects from "import { mount } from '@cypress/'" are annoying, we should avoid doing this -// by creating an explicit function/import that the user can register in their 'component.js' support file, -// such as: -// import 'cypress//support' -// or -// import { registerCT } from 'cypress/' -// registerCT() -// Note: This would be a breaking change -setupHooks(cleanup) diff --git a/npm/vue2/tsconfig.json b/npm/vue2/tsconfig.json deleted file mode 100644 index ea20f06c3e05..000000000000 --- a/npm/vue2/tsconfig.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "compilerOptions": { - /* Basic Options */ - "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, - "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, - "skipLibCheck": true, - "lib": [ - "es2015", - "dom" - ] /* Specify library files to be included in the compilation: */, - "allowJs": true /* Allow javascript files to be compiled. */, - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true /* Generates corresponding '.d.ts' file. */, - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "dist" /* Redirect output structure to the directory. */, - // "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": false /* Enable all strict type-checking options. */, - "noImplicitAny": false, - - /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - "types": [ - "cypress" - ] /* Type declaration files to be included in compilation. */, - "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - - /* Source Map Options */ - // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - "esModuleInterop": true - }, - "include": ["src/**/*.*"], - "exclude": ["src/**/*-spec.*"] -} diff --git a/npm/vue2/webpack.config.js b/npm/vue2/webpack.config.js deleted file mode 100644 index aa4b03573e40..000000000000 --- a/npm/vue2/webpack.config.js +++ /dev/null @@ -1,53 +0,0 @@ -// A basic webpack configuration -// The default for running tests in this project -// https://vue-loader.vuejs.org/guide/#manual-setup -const VueLoaderPlugin = require('vue-loader/lib/plugin') -const path = require('path') -const pkg = require('package.json') - -module.exports = { - mode: 'development', - output: { - path: path.join(__dirname, 'dist'), - filename: 'js/[name].js', - publicPath: '/', - chunkFilename: 'js/[name].js', - }, - - resolve: { - extensions: ['.js', '.json', '.vue'], - alias: { - // point at the built file - '@cypress/vue2': path.join(__dirname, pkg.main), - vue: 'vue/dist/vue.esm.js', - }, - }, - module: { - rules: [ - { - test: /\.vue$/, - loader: 'vue-loader', - }, - { - test: /\.js$/, - loader: 'babel-loader', - }, - // this will apply to both plain `.css` files - // AND ` - - -
      Deprecation Warning: The before:browser:launch plugin event changed its signature in Cypress version 4.0.0
      -
      -The event switched from yielding the second argument as an array of browser arguments to an options object with an args property.
      -
      -We've detected that your code is still using the previous, deprecated interface signature.
      -
      -This code will not work in a future version of Cypress. Please see the upgrade guide: https://on.cypress.io/deprecated-before-browser-launch-args
      -
      \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html index 3a9784cc3e03..349eec66a283 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html @@ -38,9 +38,9 @@ Please remove this flag from: /path/to/cypress.config.js -Component Testing is now a standalone command. You can now run your component tests with: +Component Testing is now a supported testing type. You can run your component tests with: - $ cypress open-ct + $ cypress open --component https://on.cypress.io/migration-guide \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_JIT_COMPILE_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_JIT_COMPILE_REMOVED.html new file mode 100644 index 000000000000..0b9be6897ffc --- /dev/null +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_JIT_COMPILE_REMOVED.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
      The experimentalJustInTimeCompile configuration option was removed in Cypress version 14.0.0.
      +A new justInTimeCompile configuration option is available and is now true by default.
      +You can safely remove this option from your config.
      +
      \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_SKIP_DOMAIN_INJECTION_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_SKIP_DOMAIN_INJECTION_REMOVED.html new file mode 100644 index 000000000000..fe4d8c55a270 --- /dev/null +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_SKIP_DOMAIN_INJECTION_REMOVED.html @@ -0,0 +1,41 @@ + + + + + + + + + + + +
      The experimentalSkipDomainInjection experiment is over. document.domain injection is now off by default.
      +
      +Read the migration guide for Cypress v14.0.0: https://on.cypress.com/migration-guide
      +
      +
      \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_USE_DEFAULT_DOCUMENT_DOMAIN_E2E_ONLY.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_USE_DEFAULT_DOCUMENT_DOMAIN_E2E_ONLY.html deleted file mode 100644 index a6426c98fed7..000000000000 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_USE_DEFAULT_DOCUMENT_DOMAIN_E2E_ONLY.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - -
      The experimentalSkipDomainInjection experiment is currently only supported for End to End Testing and must be configured as an e2e testing type property: e2e.experimentalSkipDomainInjection.
      -The suggested values are only a recommendation.
      -
      -{
      -  e2e: {
      -    experimentalSkipDomainInjection: ['*.salesforce.com', '*.force.com', '*.google.com', 'google.com']
      -  },
      -}
      -
      \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INJECT_DOCUMENT_DOMAIN_DEPRECATION.html b/packages/errors/__snapshot-html__/INJECT_DOCUMENT_DOMAIN_DEPRECATION.html new file mode 100644 index 000000000000..a1b82817b8af --- /dev/null +++ b/packages/errors/__snapshot-html__/INJECT_DOCUMENT_DOMAIN_DEPRECATION.html @@ -0,0 +1,41 @@ + + + + + + + + + + + +
      The injectDocumentDomain option is deprecated. Interactions with intra-test navigations to differing hostnames must now be wrapped in cy.origin commands, even if the hostname is a subdomain. This configuration option will be removed in Cypress 15.
      +
      +Read the documentation for the injectDocumentDomain configuration option: https://on.cypress.com/inject-document-domain-configuration
      +
      +
      \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_JIT_COMPONENT_TESTING.html b/packages/errors/__snapshot-html__/INJECT_DOCUMENT_DOMAIN_E2E_ONLY.html similarity index 69% rename from packages/errors/__snapshot-html__/EXPERIMENTAL_JIT_COMPONENT_TESTING.html rename to packages/errors/__snapshot-html__/INJECT_DOCUMENT_DOMAIN_E2E_ONLY.html index e7ed5e722e49..0b56d34e9610 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_JIT_COMPONENT_TESTING.html +++ b/packages/errors/__snapshot-html__/INJECT_DOCUMENT_DOMAIN_E2E_ONLY.html @@ -34,7 +34,8 @@ -
      The experimentalJustInTimeCompile experiment is currently only supported for Component Testing.
      +    
      The injectDocumentDomain option is only available for E2E testing.
       
      -If you have feedback about the experiment, please join the discussion here: http://on.cypress.io/just-in-time-compile
      +Read the documentation for the injectDocumentDomain configuration option: https://on.cypress.com/inject-document-domain-configuration
      +
       
      \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/JIT_COMPONENT_TESTING.html b/packages/errors/__snapshot-html__/JIT_COMPONENT_TESTING.html new file mode 100644 index 000000000000..d503011487dc --- /dev/null +++ b/packages/errors/__snapshot-html__/JIT_COMPONENT_TESTING.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
      The justInTimeCompile configuration is only supported for Component Testing.
      +
      \ No newline at end of file diff --git a/packages/errors/package.json b/packages/errors/package.json index 51c153843cc6..c5098dd2d383 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -26,7 +26,7 @@ "@packages/types": "0.0.0-development", "@types/chai": "4.2.15", "@types/mocha": "8.2.2", - "@types/node": "18.17.5", + "@types/node": "20.16.0", "@types/pngjs": "^6.0.1", "ansi-styles": "^5", "chai": "4.2.0", diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 63cf017b3caf..9df295c5ed1e 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -388,16 +388,6 @@ export const AllCypressErrors = { https://on.cypress.io/auto-cancellation-mismatch` }, - DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { - return errTemplate`\ - Deprecation Warning: The ${fmt.highlight(`before:browser:launch`)} plugin event changed its signature in ${fmt.cypressVersion(`4.0.0`)} - - The event switched from yielding the second argument as an ${fmt.highlightSecondary(`array`)} of browser arguments to an options ${fmt.highlightSecondary(`object`)} with an ${fmt.highlightSecondary(`args`)} property. - - We've detected that your code is still using the previous, deprecated interface signature. - - This code will not work in a future version of Cypress. Please see the upgrade guide: https://on.cypress.io/deprecated-before-browser-launch-args` - }, DUPLICATE_TASK_KEY: (arg1: string[]) => { return errTemplate`\ Warning: Multiple attempts to register the following task(s): @@ -1251,6 +1241,12 @@ export const AllCypressErrors = { You can safely remove this option from your config.` }, + EXPERIMENTAL_JIT_COMPILE_REMOVED: () => { + return errTemplate`\ + The ${fmt.highlight(`experimentalJustInTimeCompile`)} configuration option was removed in ${fmt.cypressVersion(`14.0.0`)}. + A new ${fmt.highlightSecondary(`justInTimeCompile`)} configuration option is available and is now ${fmt.highlightSecondary(`true`)} by default. + You can safely remove this option from your config.` + }, // TODO: verify configFile is absolute path // TODO: make this relative path, not absolute EXPERIMENTAL_COMPONENT_TESTING_REMOVED: (arg1: {configFile: string}) => { @@ -1259,9 +1255,9 @@ export const AllCypressErrors = { Please remove this flag from: ${fmt.path(arg1.configFile)} - Component Testing is now a standalone command. You can now run your component tests with: + Component Testing is now a supported testing type. You can run your component tests with: - ${fmt.terminal(`cypress open-ct`)} + ${fmt.terminal(`cypress open --component`)} https://on.cypress.io/migration-guide` }, @@ -1350,25 +1346,32 @@ export const AllCypressErrors = { ${fmt.code(code)}` }, - EXPERIMENTAL_JIT_COMPONENT_TESTING: () => { + JIT_COMPONENT_TESTING: () => { return errTemplate`\ - The ${fmt.highlight(`experimentalJustInTimeCompile`)} experiment is currently only supported for Component Testing. - - If you have feedback about the experiment, please join the discussion here: http://on.cypress.io/just-in-time-compile` + The ${fmt.highlight(`justInTimeCompile`)} configuration is only supported for Component Testing.` }, - EXPERIMENTAL_USE_DEFAULT_DOCUMENT_DOMAIN_E2E_ONLY: () => { - const code = errPartial` - { - e2e: { - experimentalSkipDomainInjection: ['*.salesforce.com', '*.force.com', '*.google.com', 'google.com'] - }, - }` + EXPERIMENTAL_SKIP_DOMAIN_INJECTION_REMOVED: () => { + return errTemplate`\ + The ${fmt.highlight(`experimentalSkipDomainInjection`)} experiment is over. ${fmt.highlight('document.domain')} injection is now off by default. + Read the migration guide for Cypress v14.0.0: https://on.cypress.com/migration-guide + ` + }, + // TODO: link to docs on injectDocumentDomain + INJECT_DOCUMENT_DOMAIN_DEPRECATION: () => { + return errTemplate`\ + The ${fmt.highlight('injectDocumentDomain')} option is deprecated. Interactions with intra-test navigations to differing hostnames must now be wrapped in ${fmt.highlight('cy.origin')} commands, even if the hostname is a subdomain. This configuration option will be removed in Cypress 15. + + Read the documentation for the injectDocumentDomain configuration option: https://on.cypress.com/inject-document-domain-configuration + ` + }, + INJECT_DOCUMENT_DOMAIN_E2E_ONLY: () => { + // TODO: link to docs on injectDocumentDomain return errTemplate`\ - The ${fmt.highlight(`experimentalSkipDomainInjection`)} experiment is currently only supported for End to End Testing and must be configured as an e2e testing type property: ${fmt.highlightSecondary(`e2e.experimentalSkipDomainInjection`)}. - The suggested values are only a recommendation. + The ${fmt.highlight('injectDocumentDomain')} option is only available for E2E testing. - ${fmt.code(code)}` + Read the documentation for the injectDocumentDomain configuration option: https://on.cypress.com/inject-document-domain-configuration + ` }, FIREFOX_GC_INTERVAL_REMOVED: () => { return errTemplate`\ @@ -1428,6 +1431,7 @@ export const AllCypressErrors = { https://on.cypress.io/component-testing` }, + UNSUPPORTED_BROWSER_VERSION: (errorMsg: string) => { return errTemplate`${fmt.off(errorMsg)}` }, @@ -1632,7 +1636,7 @@ export const AllCypressErrors = { { component: { devServer: { - framework: 'create-react-app', ${fmt.comment('// Your framework')} + framework: 'react', ${fmt.comment('// Your framework')} bundler: 'webpack' ${fmt.comment('// Your dev server')} } } diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 8960060f24fe..88d8fa118b84 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -570,11 +570,6 @@ describe('visual error templates', () => { }], } }, - DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { - return { - default: [], - } - }, DUPLICATE_TASK_KEY: () => { const tasks = ['foo', 'bar', 'baz'] @@ -1156,6 +1151,11 @@ describe('visual error templates', () => { default: [], } }, + EXPERIMENTAL_JIT_COMPILE_REMOVED: () => { + return { + default: [], + } + }, EXPERIMENTAL_COMPONENT_TESTING_REMOVED: () => { return { default: [{ configFile: '/path/to/cypress.config.js' }], @@ -1282,7 +1282,7 @@ describe('visual error templates', () => { default: [{ name: 'indexHtmlFile', configFile: '/path/to/cypress.config.js.ts' }], } }, - EXPERIMENTAL_JIT_COMPONENT_TESTING: () => { + JIT_COMPONENT_TESTING: () => { return { default: [], } @@ -1400,12 +1400,6 @@ describe('visual error templates', () => { } }, - EXPERIMENTAL_USE_DEFAULT_DOCUMENT_DOMAIN_E2E_ONLY: () => { - return { - default: [], - } - }, - PROXY_ENCOUNTERED_INVALID_HEADER_NAME: () => { const err = makeErr() @@ -1421,5 +1415,23 @@ describe('visual error templates', () => { default: [{ invalidHeaderValue: 'Value' }, 'GET', 'http://localhost:8080', err], } }, + + EXPERIMENTAL_SKIP_DOMAIN_INJECTION_REMOVED: () => { + return { + default: [], + } + }, + + INJECT_DOCUMENT_DOMAIN_DEPRECATION: () => { + return { + default: [], + } + }, + + INJECT_DOCUMENT_DOMAIN_E2E_ONLY: () => { + return { + default: [], + } + }, }) }) diff --git a/packages/frontend-shared/cypress/fixtures/config.json b/packages/frontend-shared/cypress/fixtures/config.json index a88d6e939039..9b9f487204f2 100644 --- a/packages/frontend-shared/cypress/fixtures/config.json +++ b/packages/frontend-shared/cypress/fixtures/config.json @@ -23,7 +23,6 @@ "displayName": "Chrome", "version": "97.0.4692.71", "path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", - "minSupportedVersion": 64, "majorVersion": 97 }, { @@ -33,7 +32,6 @@ "displayName": "Firefox", "version": "95.0.2", "path": "/Applications/Firefox.app/Contents/MacOS/firefox", - "minSupportedVersion": 86, "majorVersion": 95 }, { @@ -87,11 +85,6 @@ "from": "default", "field": "experimentalCspAllowList" }, - { - "value": false, - "from": "default", - "field": "experimentalFetchPolyfill" - }, { "value": true, "from": "default", diff --git a/packages/frontend-shared/cypress/support/mock-graphql/longBrowsersList.ts b/packages/frontend-shared/cypress/support/mock-graphql/longBrowsersList.ts index 68b53727e0e8..fb13244856ab 100644 --- a/packages/frontend-shared/cypress/support/mock-graphql/longBrowsersList.ts +++ b/packages/frontend-shared/cypress/support/mock-graphql/longBrowsersList.ts @@ -84,9 +84,9 @@ export const longBrowsersList = [ displayName: 'Edge Beta', family: 'chromium', channel: 'beta', - version: 'unsupported', + version: '85.0.309.71', path: '/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta', - majorVersion: '79', + majorVersion: '85', isFocusSupported: true, isVersionSupported: false, disabled: false, @@ -126,9 +126,8 @@ export const longBrowsersList = [ version: '69.0.1', path: '/Applications/Firefox/Contents/MacOS/Firefox', majorVersion: '69', - unsupportedVersion: true, isFocusSupported: true, - isVersionSupported: false, + isVersionSupported: true, disabled: true, }, { @@ -140,9 +139,8 @@ export const longBrowsersList = [ version: '75.0.1', path: '/Applications/Firefox/Contents/MacOS/Firefox', majorVersion: '75', - unsupportedVersion: true, isFocusSupported: true, - isVersionSupported: false, + isVersionSupported: true, disabled: true, }, { @@ -169,7 +167,7 @@ export const longBrowsersList = [ path: '/Applications/Firefox Nightly/Contents/MacOS/Firefox Nightly', majorVersion: '69', isFocusSupported: false, - isVersionSupported: false, + isVersionSupported: true, disabled: true, }, { @@ -182,7 +180,7 @@ export const longBrowsersList = [ path: '/Applications/Fake Browser/Contents/MacOS/Fake Browser', majorVersion: '79', isFocusSupported: false, - isVersionSupported: false, + isVersionSupported: true, disabled: true, }, ] as const diff --git a/packages/frontend-shared/cypress/support/mock-graphql/stubgql-Wizard.ts b/packages/frontend-shared/cypress/support/mock-graphql/stubgql-Wizard.ts index 2bb52a19df57..d285e694f981 100644 --- a/packages/frontend-shared/cypress/support/mock-graphql/stubgql-Wizard.ts +++ b/packages/frontend-shared/cypress/support/mock-graphql/stubgql-Wizard.ts @@ -28,25 +28,33 @@ export const allBundlers = testBundlers.map((bundler, idx) => { }) const testFrameworks = [ - { name: 'Create React App (v5)', type: 'reactscripts', supportedBundlers: [testBundlerWebpack], category: 'framework', supportStatus: 'alpha' }, + { name: 'React.js', type: 'react', supportedBundlers: [testBundlerWebpack], category: 'framework', supportStatus: 'full' }, { name: 'Vue.js (v3)', type: 'vue3', supportedBundlers: [testBundlerVite, testBundlerWebpack], category: 'library', supportStatus: 'full' }, ] as const export const stubWizard: MaybeResolver = { __typename: 'Wizard', - installDependenciesCommand: `npm install -D ${wizardDeps.WIZARD_DEPENDENCY_REACT_SCRIPTS.package} ${wizardDeps.WIZARD_DEPENDENCY_TYPESCRIPT.package}`, + installDependenciesCommand: `npm install -D ${wizardDeps.WIZARD_DEPENDENCY_REACT.package} ${wizardDeps.WIZARD_DEPENDENCY_REACT_DOM.package} ${wizardDeps.WIZARD_DEPENDENCY_TYPESCRIPT.package}`, packagesToInstall: [ { __typename: 'WizardNpmPackage', - id: 'cra', + id: 'react', satisfied: true, - ...wizardDeps.WIZARD_DEPENDENCY_REACT_SCRIPTS, + detectedVersion: '18.3.1', + ...wizardDeps.WIZARD_DEPENDENCY_REACT, + }, + { + __typename: 'WizardNpmPackage', + id: 'react-dom', + satisfied: true, + detectedVersion: '18.3.1', + ...wizardDeps.WIZARD_DEPENDENCY_REACT_DOM, }, { __typename: 'WizardNpmPackage', id: 'typescript', satisfied: false, - detectedVersion: '2.0.1', + detectedVersion: '5.4.5', ...wizardDeps.WIZARD_DEPENDENCY_TYPESCRIPT, }, ], diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index 52d2fd107a02..d40c772d1eaa 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -41,7 +41,7 @@ "@vue/compiler-core": "3.2.47", "@vue/compiler-dom": "3.2.47", "@vue/compiler-sfc": "3.2.47", - "@vue/test-utils": "2.3.2", + "@vue/test-utils": "2.4.6", "@vueuse/core": "7.2.2", "autoprefixer": "^10.4.19", "axe-core": "4.4.1", diff --git a/packages/frontend-shared/src/assets/logos/nuxt.svg b/packages/frontend-shared/src/assets/logos/nuxt.svg deleted file mode 100644 index 0517b9b66abb..000000000000 --- a/packages/frontend-shared/src/assets/logos/nuxt.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/frontend-shared/src/gql-components/HeaderBarContent.cy.tsx b/packages/frontend-shared/src/gql-components/HeaderBarContent.cy.tsx index 0d9ed5595d47..16d82495cd7f 100644 --- a/packages/frontend-shared/src/gql-components/HeaderBarContent.cy.tsx +++ b/packages/frontend-shared/src/gql-components/HeaderBarContent.cy.tsx @@ -53,11 +53,6 @@ describe('', { viewportWidth: 1000, viewportHeight: 750 }, ( }) cy.contains('Unsupported browser').should('be.visible') - - /* - TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23436 - cy.percySnapshot('unsupported browser tooltip') - */ }) describe('breadcrumbs', () => { diff --git a/packages/frontend-shared/src/gql-components/topnav/VerticalBrowserListItems.vue b/packages/frontend-shared/src/gql-components/topnav/VerticalBrowserListItems.vue index 2fcbece2ba24..d0171f0a807a 100644 --- a/packages/frontend-shared/src/gql-components/topnav/VerticalBrowserListItems.vue +++ b/packages/frontend-shared/src/gql-components/topnav/VerticalBrowserListItems.vue @@ -11,7 +11,6 @@ 'cursor-pointer': !browser.disabled && browser.isVersionSupported }" data-cy="top-nav-browser-list-item" - :data-browser-id="browser.id" @click="handleBrowserChoice(browser)" > @@ -24,7 +23,7 @@
      -
      -