Skip to content

Commit 9a533e2

Browse files
committed
WIP: play around with zoneless change detection
1 parent 0234b4d commit 9a533e2

File tree

4 files changed

+44
-42
lines changed

4 files changed

+44
-42
lines changed

angular-zoneless/cypress/support/angular-mount-playground.ts

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
import 'zone.js'
2-
3-
/**
4-
* @hack fixes "Mocha has already been patched with Zone" error.
5-
*/
6-
// @ts-ignore
7-
window.Mocha['__zone_patch__'] = false
8-
import 'zone.js/testing'
9-
101
import { CommonModule } from '@angular/common'
112
import { Component, ErrorHandler, EventEmitter, Injectable, SimpleChange, SimpleChanges, Type, OnChanges, Injector, InputSignal, WritableSignal } from '@angular/core'
123
import { toObservable } from '@angular/core/rxjs-interop'
4+
import { provideZonelessChangeDetection } from '@angular/core'
135
import {
146
ComponentFixture,
157
getTestBed,
@@ -18,9 +10,9 @@ import {
1810
TestComponentRenderer,
1911
} from '@angular/core/testing'
2012
import {
21-
BrowserDynamicTestingModule,
22-
platformBrowserDynamicTesting,
23-
} from '@angular/platform-browser-dynamic/testing'
13+
BrowserTestingModule,
14+
platformBrowserTesting
15+
} from '@angular/platform-browser/testing'
2416
import {
2517
setupHooks,
2618
getContainerEl,
@@ -162,6 +154,10 @@ function bootstrapModule<T> (
162154
useClass: CypressAngularErrorHandler,
163155
})
164156

157+
// allow for zoneless change detection inside the testing module.
158+
// @see https://angular.dev/guide/zoneless#using-zoneless-in-testbed
159+
testModuleMetaData.providers.push(provideZonelessChangeDetection())
160+
165161
// check if the component is a standalone component
166162
if ((component as any).ɵcmp?.standalone) {
167163
testModuleMetaData.imports.push(component)
@@ -244,24 +240,17 @@ function createComponentFixture<T> (
244240
* @param {Type<T>} component Angular component being mounted
245241
* @param {MountConfig<T>} config MountConfig
246242
247-
* @returns {ComponentFixture<T>} ComponentFixture
243+
* @returns {Promise<ComponentFixture<T>>} ComponentFixture
248244
*/
249-
function setupFixture<T> (
245+
async function setupFixture<T> (
250246
component: Type<T>,
251247
config: MountConfig<T>,
252-
): ComponentFixture<T> {
248+
): Promise<ComponentFixture<T>> {
253249
const fixture = getTestBed().createComponent(component)
254250

255251
setupComponent(config, fixture)
256252

257-
fixture.whenStable().then(() => {
258-
fixture.autoDetectChanges(config.autoDetectChanges ?? true)
259-
}).catch((e) => {
260-
// If this promise does not settle in Angular 19 it is rejected
261-
// https://github.com/angular/angular/blob/main/CHANGELOG.md#1900-2024-11-19
262-
// eslint-disable-next-line no-console
263-
console.error(e)
264-
})
253+
await fixture.whenStable()
265254

266255
return fixture
267256
}
@@ -529,22 +518,34 @@ export function mount<T> (
529518

530519
const componentFixture = initTestBed(component, config)
531520

532-
activeFixture = setupFixture(componentFixture, config)
533-
534-
const mountResponse: MountResponse<T> = {
535-
fixture: activeFixture,
536-
component: activeFixture.componentInstance,
537-
}
521+
let mountResponsePromiseResolver: any
522+
let mountResponsePromiseRejector: any
523+
let mountResponsePromise: Promise<MountResponse<T>> = new Promise((resolve, reject) => {
524+
mountResponsePromiseResolver = resolve
525+
mountResponsePromiseRejector = reject
526+
})
538527

539-
const logMessage = typeof component === 'string' ? 'Component' : componentFixture.name
528+
setupFixture(componentFixture, config).then((fixture) => {
529+
activeFixture = fixture
530+
const mountResponse: MountResponse<T> = {
531+
fixture: activeFixture,
532+
component: activeFixture.componentInstance,
533+
}
534+
535+
const logMessage = typeof component === 'string' ? 'Component' : componentFixture.name
536+
537+
Cypress.log({
538+
name: 'mount',
539+
message: logMessage,
540+
consoleProps: () => ({ result: mountResponse }),
541+
})
540542

541-
Cypress.log({
542-
name: 'mount',
543-
message: logMessage,
544-
consoleProps: () => ({ result: mountResponse }),
543+
mountResponsePromiseResolver(mountResponse)
544+
}).catch((error) => {
545+
mountResponsePromiseRejector(error)
545546
})
546547

547-
return cy.wrap(mountResponse, { log: false })
548+
return cy.wrap(mountResponsePromise, { log: false })
548549
}
549550

550551
/**
@@ -579,10 +580,9 @@ export const createOutputSpy = <T>(alias: string) => {
579580
return emitter as any
580581
}
581582

582-
// Only needs to run once, we reset before each test
583583
getTestBed().initTestEnvironment(
584-
BrowserDynamicTestingModule,
585-
platformBrowserDynamicTesting(),
584+
BrowserTestingModule,
585+
platformBrowserTesting(),
586586
{
587587
teardown: { destroyAfterEach: false },
588588
},

angular-zoneless/src/app/app.component.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CommonModule } from '@angular/common';
22
import { HttpClientModule } from '@angular/common/http';
3-
import { Component } from '@angular/core';
3+
import { ChangeDetectorRef, Component } from '@angular/core';
44
import { take } from 'rxjs/operators';
55
import { LoginFormComponent } from './login-form/login-form.component';
66
import { LoginService } from './login.service';
@@ -17,7 +17,7 @@ export class AppComponent {
1717
errorMessage = '';
1818
username = ''
1919

20-
constructor(private readonly loginService: LoginService) {}
20+
constructor(private readonly loginService: LoginService, private ref: ChangeDetectorRef) {}
2121

2222
handleLogin(username: string, password: string): void {
2323
this.errorMessage = '';
@@ -31,6 +31,8 @@ export class AppComponent {
3131
} else {
3232
this.errorMessage = response.message
3333
}
34+
// force change detection to update the UI until we convert the values to signals
35+
this.ref.markForCheck();
3436
})
3537
}
3638

angular-zoneless/src/app/test-component-signals-example/test-component.component.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { signal } from '@angular/core';
22
import { TestComponent } from './test-component.component';
3-
import { createOutputSpy } from 'cypress/angular';
3+
import { createOutputSpy } from '../../../cypress/support/angular-mount-playground';
44

55
describe('TestComponent', () => {
66
it('works with inferred generic type input<required>', () => {

angular-zoneless/tsconfig.app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
],
1212
"exclude": [
1313
"src/**/*.spec.ts",
14-
"src/**/*.cy.ts"
14+
// "src/**/*.cy.ts"
1515
]
1616
}

0 commit comments

Comments
 (0)