From ddd40669a24b6be63c858f4420629312dde13a9f Mon Sep 17 00:00:00 2001 From: AtofStryker Date: Mon, 4 Nov 2024 16:03:34 -0500 Subject: [PATCH] breaking: remove the cypress/angular-signals test harness. The changes incorporated in @cypress/angular-signals are now merged upstream into @cypress/angular and the package is no longer needed [run ci] --- .circleci/cache-version.txt | 2 +- .circleci/workflows.yml | 27 +- CHANGELOG.md | 1 - CONTRIBUTING.md | 1 - cli/.eslintignore | 1 - cli/.gitignore | 1 - cli/CHANGELOG.md | 1 + cli/package.json | 9 +- cli/scripts/post-build.js | 1 - npm/angular-signals/.eslintignore | 5 - npm/angular-signals/.eslintrc | 8 - npm/angular-signals/.npmignore | 3 - npm/angular-signals/.releaserc.js | 3 - npm/angular-signals/CHANGELOG.md | 6 - npm/angular-signals/README.md | 11 - npm/angular-signals/package.json | 74 --- npm/angular-signals/rollup.config.mjs | 14 - npm/angular-signals/src/index.ts | 1 - npm/angular-signals/src/mount.ts | 554 ------------------ npm/angular-signals/tsconfig.json | 26 - npm/angular/package.json | 13 +- npm/angular/src/mount.ts | 164 +++++- .../cypress/support/component.ts | 2 +- .../signals-complex.component.cy.ts | 2 +- .../signals-optional.component.cy.ts | 2 +- .../signals-required.component.cy.ts | 2 +- yarn.lock | 9 +- 27 files changed, 185 insertions(+), 758 deletions(-) delete mode 100644 npm/angular-signals/.eslintignore delete mode 100644 npm/angular-signals/.eslintrc delete mode 100644 npm/angular-signals/.npmignore delete mode 100644 npm/angular-signals/.releaserc.js delete mode 100644 npm/angular-signals/CHANGELOG.md delete mode 100644 npm/angular-signals/README.md delete mode 100644 npm/angular-signals/package.json delete mode 100644 npm/angular-signals/rollup.config.mjs delete mode 100644 npm/angular-signals/src/index.ts delete mode 100644 npm/angular-signals/src/mount.ts delete mode 100644 npm/angular-signals/tsconfig.json diff --git a/.circleci/cache-version.txt b/.circleci/cache-version.txt index c33910231309..0bb25c86b55e 100644 --- a/.circleci/cache-version.txt +++ b/.circleci/cache-version.txt @@ -1,3 +1,3 @@ # Bump this version to force CI to re-create the cache from scratch. -11-6-24 +11-06-24-angular-signals-removal diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index dbacc09a4ffe..6fc8e7209593 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -30,7 +30,7 @@ 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' - - 'breaking/remove_vue2_support' + - 'breaking/remove_angular_signals_test_harness' - 'publish-binary' # usually we don't build Mac app - it takes a long time @@ -42,7 +42,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: [ 'breaking/remove_vue2_support', << pipeline.git.branch >> ] + - equal: [ 'breaking/remove_angular_signals_test_harness', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -53,7 +53,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: [ 'breaking/remove_vue2_support', << pipeline.git.branch >> ] + - equal: [ 'breaking/remove_angular_signals_test_harness', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -76,7 +76,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: [ 'breaking/remove_vue2_support', << pipeline.git.branch >> ] + - equal: [ 'breaking/remove_angular_signals_test_harness', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -152,7 +152,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" != "breaking/remove_vue2_support" ]]; then + echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "breaking/remove_angular_signals_test_harness" ]]; 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 @@ -2163,15 +2163,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: @@ -2943,9 +2934,6 @@ linux-x64-workflow: &linux-x64-workflow - npm-angular: requires: - build - - npm-angular-signals: - requires: - - build - npm-mount-utils: requires: - build @@ -2964,7 +2952,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 @@ -3302,9 +3289,6 @@ linux-x64-contributor-workflow: &linux-x64-contributor-workflow - npm-angular: requires: - build - - npm-angular-signals: - requires: - - build - npm-mount-utils: requires: - build @@ -3322,7 +3306,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/CHANGELOG.md b/CHANGELOG.md index 0bd17c07c3a2..71f07af48ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ - [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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc89b8bc1db2..530134c6218f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -182,7 +182,6 @@ 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. | diff --git a/cli/.eslintignore b/cli/.eslintignore index 5132064ff61c..9c171ae7d57e 100644 --- a/cli/.eslintignore +++ b/cli/.eslintignore @@ -10,7 +10,6 @@ package.json # these are all copied from dist'd builds from the individual libs /angular -/angular-signals /react /react18 /vue diff --git a/cli/.gitignore b/cli/.gitignore index c6cf7aaf489a..1c68b02ce1e5 100644 --- a/cli/.gitignore +++ b/cli/.gitignore @@ -20,4 +20,3 @@ react* mount-utils angular svelte -angular-signals diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 77e6ffb1eb1d..08131bb86c47 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -21,6 +21,7 @@ _Released 12/3/2024 (PENDING)_ - Cypress Component Testing no longer supports `Vue` version 2. Addresses [#30295](https://github.com/cypress-io/cypress/issues/30295). - Cypress Component Testing no longer supports `Next.js` versions 10, 11, 12, and 13. Addresses [#29583](https://github.com/cypress-io/cypress/issues/29583). - Cypress Component Testing no longer supports `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). +- 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). **Deprecations:** diff --git a/cli/package.json b/cli/package.json index e4e4039fb9e8..63ae512b0bf7 100644 --- a/cli/package.json +++ b/cli/package.json @@ -68,7 +68,6 @@ "@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", @@ -117,8 +116,7 @@ "react", "react18", "angular", - "svelte", - "angular-signals" + "svelte" ], "bin": { "cypress": "bin/cypress" @@ -165,11 +163,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 5a225cf7d409..4ee23008cf03 100644 --- a/cli/scripts/post-build.js +++ b/cli/scripts/post-build.js @@ -12,7 +12,6 @@ const npmModulesToCopy = [ 'react18', 'vue', 'angular', - 'angular-signals', 'svelte', ] 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/package.json b/npm/angular/package.json index c7b1541fb207..a2b02d0cecf4 100644 --- a/npm/angular/package.json +++ b/npm/angular/package.json @@ -1,13 +1,14 @@ { "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": {}, @@ -16,14 +17,16 @@ "@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": ">=17.2", "@angular/core": ">=17.2", "@angular/platform-browser-dynamic": ">=17.2", - "zone.js": ">=0.11.0" + "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..140b2215f248 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 = [] } /** @@ -254,6 +265,129 @@ function setupFixture ( 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 +400,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 +524,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/system-tests/projects/angular-signals/cypress/support/component.ts b/system-tests/projects/angular-signals/cypress/support/component.ts index 958e21d484f5..381375eef6a2 100644 --- a/system-tests/projects/angular-signals/cypress/support/component.ts +++ b/system-tests/projects/angular-signals/cypress/support/component.ts @@ -1,4 +1,4 @@ -import { mount } from 'cypress/angular-signals' +import { mount } from 'cypress/angular' // Augment the Cypress namespace to include type definitions for // your custom command. diff --git a/system-tests/projects/angular-signals/src/signals-complex/signals-complex.component.cy.ts b/system-tests/projects/angular-signals/src/signals-complex/signals-complex.component.cy.ts index 998098cd8a51..10cfb04d02d0 100644 --- a/system-tests/projects/angular-signals/src/signals-complex/signals-complex.component.cy.ts +++ b/system-tests/projects/angular-signals/src/signals-complex/signals-complex.component.cy.ts @@ -1,6 +1,6 @@ import { signal } from '@angular/core' import { SignalsComplexComponent } from './signals-complex.component' -import { createOutputSpy } from 'cypress/angular-signals' +import { createOutputSpy } from 'cypress/angular' import cloneDeep from 'lodash/cloneDeep' const user = { diff --git a/system-tests/projects/angular-signals/src/signals-optional/signals-optional.component.cy.ts b/system-tests/projects/angular-signals/src/signals-optional/signals-optional.component.cy.ts index 544b864db358..855040a5cda5 100644 --- a/system-tests/projects/angular-signals/src/signals-optional/signals-optional.component.cy.ts +++ b/system-tests/projects/angular-signals/src/signals-optional/signals-optional.component.cy.ts @@ -1,6 +1,6 @@ import { signal } from '@angular/core' import { SignalsOptionalComponent } from './signals-optional.component' -import { createOutputSpy } from 'cypress/angular-signals' +import { createOutputSpy } from 'cypress/angular' it('can handle default props', () => { cy.mount(SignalsOptionalComponent) diff --git a/system-tests/projects/angular-signals/src/signals-required/signals-required.component.cy.ts b/system-tests/projects/angular-signals/src/signals-required/signals-required.component.cy.ts index a1a47bd8d805..37949b419c06 100644 --- a/system-tests/projects/angular-signals/src/signals-required/signals-required.component.cy.ts +++ b/system-tests/projects/angular-signals/src/signals-required/signals-required.component.cy.ts @@ -1,6 +1,6 @@ import { signal, computed } from '@angular/core' import { SignalsRequiredComponent } from './signals-required.component' -import { createOutputSpy } from 'cypress/angular-signals' +import { createOutputSpy } from 'cypress/angular' // NOTE: if this is the only test in your test suite, this error will continually throw until the fixture is closed. it('errors on required props missing', (done) => { diff --git a/yarn.lock b/yarn.lock index 08807664be81..402887153a9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27333,7 +27333,7 @@ rollup@^3.27.1: optionalDependencies: fsevents "~2.3.2" -rollup@^4.13.0, rollup@^4.20.0: +rollup@^4.13.0, rollup@^4.20.0, rollup@^4.24.4: version "4.24.4" resolved "https://registry.npmjs.org/rollup/-/rollup-4.24.4.tgz#fdc76918de02213c95447c9ffff5e35dddb1d058" integrity sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA== @@ -32927,13 +32927,6 @@ zone.js@0.9.0: resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.9.0.tgz#f42319d657f7616724ed40c5907d4614b4c683fa" integrity sha512-EfygvVnLxPSCMSgJ4h7SoY+XNr7ybdwvvwEQ70lvMFl9coNnciXSyWi8Kg6znK1ubyUSffkCKvleSQpLuUKw0Q== -zone.js@~0.11.4: - version "0.11.7" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.7.tgz#262194267c7b964e8da77ce16b9fba9bea23cfdc" - integrity sha512-e39K2EdK5JfA3FDuUTVRvPlYV4aBfnOOcGuILhQAT7nzeV12uSrLBzImUM9CDVoncDSX4brR/gwqu0heQ3BQ0g== - dependencies: - tslib "^2.3.0" - zone.js@~0.14.6: version "0.14.6" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.14.6.tgz#9a74284f4812b807c97d942e78166cb2da8b5614"