mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
test(perf): count change-detection ticks in the electron-performance build (#1776)
* test(perf): count change-detection ticks in the electron-performance build J1 renderer.cdTicksToFirstCard, J2 renderer.cdTicksToFirstPage and the J1 idle baseline renderer.cdTicksIdle30s. Angular's ɵsetProfiler is only reachable through the dev-mode window.ng global, so the electron-performance configuration alone swaps environment.ts for environment.performance.ts, which re-exports the production AppConfig and wraps ApplicationRef._tick. Production and PWA sources and output are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(perf): refuse a J1 idle window that opened late after the settle point Addresses review: the idle window opens in the settle timer's callback while the settle point is that timer's deadline, so a late callback left ticks uncounted between the two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
20 files changed
+778
-81
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import { ApplicationRef, Component, signal } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
CHANGE_DETECTION_TICK_COUNTER_KEY,
|
||||
installChangeDetectionTickCounter,
|
||||
} from './change-detection-tick-counter';
|
||||
|
||||
describe('installChangeDetectionTickCounter', () => {
|
||||
it('counts every _tick call and forwards to the original', () => {
|
||||
const calls: unknown[] = [];
|
||||
const prototype = {
|
||||
_tick(this: unknown) {
|
||||
calls.push(this);
|
||||
},
|
||||
};
|
||||
const target: Record<string, unknown> = {};
|
||||
|
||||
const counter = installChangeDetectionTickCounter(target, prototype);
|
||||
const instance = {};
|
||||
prototype._tick.call(instance);
|
||||
prototype._tick.call(instance);
|
||||
|
||||
expect(counter.count).toBe(2);
|
||||
expect(calls).toEqual([instance, instance]);
|
||||
expect(target[CHANGE_DETECTION_TICK_COUNTER_KEY]).toBe(counter);
|
||||
});
|
||||
|
||||
it('installs once per target', () => {
|
||||
const prototype = { _tick: jest.fn() };
|
||||
const target: Record<string, unknown> = {};
|
||||
|
||||
const first = installChangeDetectionTickCounter(target, prototype);
|
||||
const second = installChangeDetectionTickCounter(target, prototype);
|
||||
prototype._tick();
|
||||
|
||||
expect(second).toBe(first);
|
||||
expect(first.count).toBe(1);
|
||||
});
|
||||
|
||||
it('fails when the internal tick method is missing', () => {
|
||||
expect(() => installChangeDetectionTickCounter({}, {})).toThrow(
|
||||
'change-detection-tick-counter-hook-missing'
|
||||
);
|
||||
});
|
||||
|
||||
it('counts the ticks of the installed Angular version', () => {
|
||||
@Component({ template: '{{ value() }}' })
|
||||
class CounterHostComponent {
|
||||
readonly value = signal(0);
|
||||
}
|
||||
const prototype = ApplicationRef.prototype as unknown as {
|
||||
_tick: () => void;
|
||||
};
|
||||
const original = prototype._tick;
|
||||
try {
|
||||
const counter = installChangeDetectionTickCounter({});
|
||||
const fixture = TestBed.createComponent(CounterHostComponent);
|
||||
const appRef = TestBed.inject(ApplicationRef);
|
||||
appRef.attachView(fixture.componentRef.hostView);
|
||||
const before = counter.count;
|
||||
|
||||
appRef.tick();
|
||||
fixture.componentInstance.value.set(1);
|
||||
appRef.tick();
|
||||
|
||||
expect(counter.count - before).toBe(2);
|
||||
expect(fixture.nativeElement.textContent).toBe('1');
|
||||
} finally {
|
||||
prototype._tick = original;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { ApplicationRef } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Change-detection tick counter for the performance journeys. Only the
|
||||
* `electron-performance` build of `apps/web` contains this module: it is
|
||||
* imported by `environment.performance.ts`, which `fileReplacements` swaps
|
||||
* in for `environment.ts` in that configuration alone. The production and
|
||||
* PWA builds never reference it, so their output is unchanged.
|
||||
*
|
||||
* Angular's own hook, `ɵsetProfiler`, is reachable only through the dev-mode
|
||||
* `window.ng` global, which the optimized build does not publish. Every
|
||||
* tick, whether scheduled by zone.js (`onMicrotaskEmpty`), the zoneless
|
||||
* scheduler or an explicit `ApplicationRef.tick()`, goes through the
|
||||
* internal `ApplicationRef._tick`, which is also where Angular emits the
|
||||
* profiler's `ChangeDetectionStart`. Counting its calls therefore matches
|
||||
* the profiler's count and stays comparable across the zoneless migration.
|
||||
* Contract: docs/architecture/performance-journeys.md.
|
||||
*/
|
||||
export const CHANGE_DETECTION_TICK_COUNTER_KEY = '__iptvnatorCdTicks';
|
||||
|
||||
export interface ChangeDetectionTickCounter {
|
||||
/** `ApplicationRef._tick` calls since the counter was installed. */
|
||||
readonly count: number;
|
||||
readonly schemaVersion: 1;
|
||||
}
|
||||
|
||||
type TickPrototype = { _tick?: (this: ApplicationRef) => void };
|
||||
|
||||
export function installChangeDetectionTickCounter(
|
||||
target: Record<string, unknown> = globalThis as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>,
|
||||
prototype: TickPrototype = ApplicationRef.prototype as unknown as TickPrototype
|
||||
): ChangeDetectionTickCounter {
|
||||
const existing = target[CHANGE_DETECTION_TICK_COUNTER_KEY];
|
||||
if (existing !== undefined) {
|
||||
return existing as ChangeDetectionTickCounter;
|
||||
}
|
||||
const original = prototype._tick;
|
||||
// A renamed internal must fail the performance build loudly, never
|
||||
// report zero ticks.
|
||||
if (typeof original !== 'function') {
|
||||
throw new Error('change-detection-tick-counter-hook-missing');
|
||||
}
|
||||
const counter = { count: 0, schemaVersion: 1 as const };
|
||||
prototype._tick = function countedTick(this: ApplicationRef): void {
|
||||
counter.count += 1;
|
||||
original.call(this);
|
||||
};
|
||||
Object.defineProperty(target, CHANGE_DETECTION_TICK_COUNTER_KEY, {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
value: counter,
|
||||
writable: false,
|
||||
});
|
||||
return counter;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// The electron-performance build's environment: production values plus the
|
||||
// change-detection tick counter the performance journeys read. See
|
||||
// change-detection-tick-counter.ts; no other configuration imports this file.
|
||||
import { installChangeDetectionTickCounter } from './change-detection-tick-counter';
|
||||
|
||||
installChangeDetectionTickCounter();
|
||||
|
||||
export { AppConfig } from './environment.prod';
|
||||
Reference in new issue
Block a user