mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
test(devtools): run the orphaned app component spec
`app.component.spec.ts` has been in the tree since the initial DevTools commit in 2020, but no revision of `shell-browser/src/app/BUILD.bazel` has ever listed it, and that file has no globs. Two of the three specs in the directory are wired up; this one has never run. It could not run as written. The setup declared `AppComponent` and imported `RouterModule.forRoot([])`, neither of which applies now that the component is standalone and injects `MessageBus` and `DEEP_LINK_INSTANCE_ID`. It now stubs the `chrome.devtools` surface that `ngOnInit` reads and covers both lifecycle hooks: the listeners registered on init, the two performance track emits, the three guards on the deep link listener, and the listeners removed on destroy. Follow-up to #70570, which fixed the same problem in ng-devtools-backend.
This commit is contained in:
committed by
Kristiyan Kostadinov
parent
635ef1bd80
commit
d6d51a0ef8
@@ -34,6 +34,25 @@ ng_project(
|
||||
],
|
||||
)
|
||||
|
||||
ts_test_library(
|
||||
name = "app_component_test_lib",
|
||||
srcs = ["app.component.spec.ts"],
|
||||
deps = [
|
||||
":app",
|
||||
"//:node_modules/@angular/core",
|
||||
"//devtools/projects/ng-devtools",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-providers:deep_link",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
|
||||
zoneless_web_test_suite(
|
||||
name = "app_component_test",
|
||||
deps = [
|
||||
":app_component_test_lib",
|
||||
],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "ng_validate",
|
||||
srcs = [
|
||||
|
||||
@@ -6,30 +6,150 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {ApplicationOperations} from '../../../ng-devtools';
|
||||
import {Component} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {DevToolsComponent} from '../../../ng-devtools';
|
||||
import {DEEP_LINK_INSTANCE_ID} from '../../../ng-devtools/src/lib/application-providers/deep_link';
|
||||
import {Events, MessageBus} from '../../../protocol';
|
||||
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
const applicationOperationsSPy = jasmine.createSpyObj('messageBus', ['viewSource']);
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [RouterModule.forRoot([])],
|
||||
providers: [
|
||||
{
|
||||
provide: ApplicationOperations,
|
||||
useClass: applicationOperationsSPy,
|
||||
},
|
||||
],
|
||||
});
|
||||
}));
|
||||
@Component({
|
||||
selector: 'ng-devtools',
|
||||
template: '',
|
||||
})
|
||||
export class MockDevToolsComponent {}
|
||||
|
||||
it('should create the app', () => {
|
||||
interface ChromeEvent {
|
||||
addListener(callback: () => void): void;
|
||||
removeListener(callback: () => void): void;
|
||||
}
|
||||
|
||||
/** An instance id that no test sets, so it detects a listener that should not have fired. */
|
||||
const UNTOUCHED = -1;
|
||||
|
||||
/** Dispatches a deep link message on the window. */
|
||||
function postDeepLink(data: unknown, origin = window.location.origin): void {
|
||||
window.dispatchEvent(new MessageEvent('message', {origin, data}));
|
||||
}
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let messageBus: jasmine.SpyObj<MessageBus<Events>>;
|
||||
let onNavigated: jasmine.SpyObj<ChromeEvent>;
|
||||
let onProfilingStarted: jasmine.SpyObj<ChromeEvent>;
|
||||
let onProfilingStopped: jasmine.SpyObj<ChromeEvent>;
|
||||
|
||||
async function createFixture(): Promise<ComponentFixture<AppComponent>> {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
await fixture.whenStable();
|
||||
return fixture;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
messageBus = jasmine.createSpyObj('MessageBus', ['on', 'once', 'emit', 'destroy']);
|
||||
onNavigated = jasmine.createSpyObj('onNavigated', ['addListener', 'removeListener']);
|
||||
onProfilingStarted = jasmine.createSpyObj('onProfilingStarted', [
|
||||
'addListener',
|
||||
'removeListener',
|
||||
]);
|
||||
onProfilingStopped = jasmine.createSpyObj('onProfilingStopped', [
|
||||
'addListener',
|
||||
'removeListener',
|
||||
]);
|
||||
|
||||
(globalThis as any).chrome = {
|
||||
devtools: {
|
||||
network: {onNavigated},
|
||||
performance: {onProfilingStarted, onProfilingStopped},
|
||||
},
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: MessageBus, useValue: messageBus}],
|
||||
}).overrideComponent(AppComponent, {
|
||||
remove: {imports: [DevToolsComponent]},
|
||||
add: {imports: [MockDevToolsComponent]},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
delete (globalThis as any).chrome;
|
||||
});
|
||||
|
||||
it('should create the app', async () => {
|
||||
const fixture = await createFixture();
|
||||
|
||||
expect(fixture.componentInstance).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should listen for navigations and profiling on init', async () => {
|
||||
await createFixture();
|
||||
|
||||
expect(onNavigated.addListener).toHaveBeenCalled();
|
||||
expect(onProfilingStarted.addListener).toHaveBeenCalled();
|
||||
expect(onProfilingStopped.addListener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit when profiling starts and stops', async () => {
|
||||
await createFixture();
|
||||
|
||||
onProfilingStarted.addListener.calls.mostRecent().args[0]();
|
||||
expect(messageBus.emit).toHaveBeenCalledWith('enablePerformanceTrack');
|
||||
|
||||
onProfilingStopped.addListener.calls.mostRecent().args[0]();
|
||||
expect(messageBus.emit).toHaveBeenCalledWith('disablePerformanceTrack');
|
||||
});
|
||||
|
||||
it('should stop listening for profiling once destroyed', async () => {
|
||||
const fixture = await createFixture();
|
||||
fixture.destroy();
|
||||
|
||||
expect(onProfilingStarted.removeListener).toHaveBeenCalled();
|
||||
expect(onProfilingStopped.removeListener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should record the instance id sent by a same origin deep link', async () => {
|
||||
await createFixture();
|
||||
|
||||
postDeepLink({type: 'angular-devtools-deep-link', instanceId: 7});
|
||||
|
||||
expect(TestBed.inject(DEEP_LINK_INSTANCE_ID)()).toBe(7);
|
||||
});
|
||||
|
||||
it('should ignore a deep link from another origin', async () => {
|
||||
await createFixture();
|
||||
TestBed.inject(DEEP_LINK_INSTANCE_ID).set(UNTOUCHED);
|
||||
|
||||
postDeepLink({type: 'angular-devtools-deep-link', instanceId: 7}, 'https://malicious.example');
|
||||
|
||||
expect(TestBed.inject(DEEP_LINK_INSTANCE_ID)()).toBe(UNTOUCHED);
|
||||
});
|
||||
|
||||
it('should ignore a message of another type', async () => {
|
||||
await createFixture();
|
||||
TestBed.inject(DEEP_LINK_INSTANCE_ID).set(UNTOUCHED);
|
||||
|
||||
postDeepLink({type: 'some-other-message', instanceId: 7});
|
||||
|
||||
expect(TestBed.inject(DEEP_LINK_INSTANCE_ID)()).toBe(UNTOUCHED);
|
||||
});
|
||||
|
||||
it('should ignore a deep link whose instance id is not a number', async () => {
|
||||
await createFixture();
|
||||
TestBed.inject(DEEP_LINK_INSTANCE_ID).set(UNTOUCHED);
|
||||
|
||||
postDeepLink({type: 'angular-devtools-deep-link', instanceId: '7'});
|
||||
|
||||
expect(TestBed.inject(DEEP_LINK_INSTANCE_ID)()).toBe(UNTOUCHED);
|
||||
});
|
||||
|
||||
it('should stop listening for deep links once destroyed', async () => {
|
||||
const fixture = await createFixture();
|
||||
TestBed.inject(DEEP_LINK_INSTANCE_ID).set(UNTOUCHED);
|
||||
fixture.destroy();
|
||||
|
||||
postDeepLink({type: 'angular-devtools-deep-link', instanceId: 7});
|
||||
|
||||
expect(TestBed.inject(DEEP_LINK_INSTANCE_ID)()).toBe(UNTOUCHED);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user