Skip to content

Commit a9f55b3

Browse files
feat(http): add SSR overrides and time until the app is stable (#242)
* feat(http): add SSR overrides and time until the app is stable Issue #32 asked for ways to change server responses on purpose, and for the time until the page is interactive. Neither existed. Add dev-only SSR overrides, set from a new section of the SSR & HTTP tab and kept in the server's memory behind actions.http: - Render error throws while the server renders a matching page, from providePangularHttp(), so the user sees the server's real error path. - Force Client render serves index.csr.html from ssrMiddleware instead of rendering, when initPangularHub() gets browserDistFolder. - Edit TransferState sets or removes an entry of the {appId}-state script before the HTML is sent, escaped the way Angular writes it. Each traced request lists the overrides that matched and what they did, is marked overridden in the panel, and explain-ssr-request says so. The overlay also reports when the app first became stable, measured from navigation start, as App stable after. Refs #32 * feat(demo): pass browserDistFolder so SSR overrides can force a Client render The Force Client render override serves index.csr.html, which the hub can only find when it knows the browser build folder. Pass it in server.ts so the demo can show the override. Refs #32 * docs(docs): document SSR overrides and time until stable Refs #32 * fixup! feat(http): add SSR overrides and time until the app is stable
1 parent 6642fb9 commit a9f55b3

22 files changed

Lines changed: 1157 additions & 38 deletions
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { TestBed, type ComponentFixture } from '@angular/core/testing';
2+
import { afterEach, describe, expect, it, vi } from 'vitest';
3+
4+
const calls: { name: string; args: unknown[] }[] = [];
5+
vi.mock('../rpc', () => ({
6+
rpcCall: async (_rpc: unknown, name: string, ...args: unknown[]) => {
7+
calls.push({ name, args });
8+
return args[0];
9+
},
10+
}));
11+
12+
const { NetworkInspector } = await import('../pages/network-inspector');
13+
14+
let fixture: ComponentFixture<InstanceType<typeof NetworkInspector>>;
15+
16+
describe('NetworkInspector SSR overrides', () => {
17+
afterEach(() => {
18+
calls.length = 0;
19+
fixture?.destroy();
20+
TestBed.resetTestingModule();
21+
});
22+
23+
it('adds a TransferState edit only with a key and valid JSON', async () => {
24+
fixture = TestBed.createComponent(NetworkInspector);
25+
const inspector = fixture.componentInstance;
26+
await fixture.whenStable();
27+
inspector.setOverrideDraft('kind', 'state-edit');
28+
inspector.setOverrideDraft('pattern', '/examples/ssr');
29+
expect(inspector.overrideReady()).toBe(false);
30+
inspector.setOverrideDraft('key', 'abc');
31+
inspector.setOverrideDraft('value', '{bad');
32+
expect(inspector.overrideValueError()).toBe('The value must be valid JSON.');
33+
expect(inspector.overrideReady()).toBe(false);
34+
inspector.setOverrideDraft('value', '{"b":2}');
35+
await inspector.addOverride(new Event('submit'));
36+
expect(calls[0].name).toBe('set-ssr-overrides');
37+
expect(calls[0].args[0]).toEqual([
38+
expect.objectContaining({
39+
kind: 'state-edit',
40+
pattern: '/examples/ssr',
41+
key: 'abc',
42+
value: '{"b":2}',
43+
}),
44+
]);
45+
expect(inspector.overrides()).toHaveLength(1);
46+
});
47+
48+
it('marks an overridden request and lists what was applied', async () => {
49+
fixture = TestBed.createComponent(NetworkInspector);
50+
const inspector = fixture.componentInstance;
51+
inspector.requests.set([
52+
{
53+
id: 'r1',
54+
method: 'GET',
55+
url: '/examples/ssr',
56+
status: 200,
57+
at: 1,
58+
durationMs: 1,
59+
renderMs: 1,
60+
bytes: 1,
61+
renderMode: 'client',
62+
fetches: 0,
63+
fetchMs: 0,
64+
headers: {},
65+
overrides: [
66+
{ id: 'o1', kind: 'client-render', applied: true, note: 'served index.csr.html' },
67+
],
68+
},
69+
]);
70+
inspector.selectedRequestId.set('r1');
71+
await fixture.whenStable();
72+
const host = fixture.nativeElement as HTMLElement;
73+
expect(host.querySelector('tbody .tag.mock')!.textContent).toBe('overridden');
74+
const detail = host.querySelector('#ssr-detail')!.textContent!;
75+
expect(detail).toContain('Overrides from the panel');
76+
expect(detail).toContain('Client render');
77+
expect(detail).toContain('served index.csr.html');
78+
});
79+
});

0 commit comments

Comments
 (0)