Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions packages/devtools/src/__tests__/analog-load-inherit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// @vitest-environment jsdom
import '@angular/compiler';
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
import { Router, RouterOutlet, provideRouter, type Route, type Routes } from '@angular/router';
import { describe, expect, it } from 'vitest';
import { ANALOG_META_DESCRIPTION, chainOf, collectAnalog } from '../analog-runtime.ts';
import { scanAnalog } from '../rpc/analog-scan.ts';
import { resolveAnalogReport } from '../rpc/analog-tools.ts';
import { BASE_FILES, makeProject } from './analog-fixture.ts';

TestBed.initTestEnvironment(BrowserTestingModule, platformBrowserTesting());

const META = Symbol(ANALOG_META_DESCRIPTION);

function analogRoute(endpoint: string, endpointKey: string, route: Route): Route {
return Object.assign(route, { [META]: { endpoint, endpointKey } });
}

class ProductsLayout {}
Component({ imports: [RouterOutlet], template: '<router-outlet />' })(ProductsLayout);

class ProductList {}
Component({ template: '<p>list</p>' })(ProductList);

describe('Analog load() of a layout', () => {
it('stays with the layout when an empty-path child page inherits it', async () => {
const routes: Routes = [
{
path: 'products',
children: [
analogRoute('/pages/products', '/src/app/pages/products.server.ts', {
path: '',
component: ProductsLayout,
resolve: { load: () => ({ items: [1, 2] }) },
children: [
analogRoute('/pages/products/index', '/src/app/pages/products/index.server.ts', {
path: '',
component: ProductList,
}),
],
}),
],
},
];
TestBed.configureTestingModule({ providers: [provideRouter(routes)] });
const router = TestBed.inject(Router);
await router.navigateByUrl('/products');

const { chain, data, loadFrom } = chainOf(
router.routerState.snapshot.root as unknown as Record<string, any>,
);
expect(chain.map((c) => c.serverFile)).toEqual([
'/src/app/pages/products.server.ts',
'/src/app/pages/products/index.server.ts',
]);
expect(data).toEqual({ items: [1, 2] });
// The list page has no .server.ts: its `data.load` is the layout's,
// inherited by Angular because its path is empty.

document.body.innerHTML = '<app-root ng-version="22"></app-root>';
const project = scanAnalog(
makeProject(BASE_FILES, {
'src/app/pages/products.server.ts': 'export const load = async () => ({ items: [] });\n',
}),
);
const report = collectAnalog(
{ ɵgetRouterInstance: () => router, getInjector: () => null },
'p1',
[],
() => ({ hydrated: 0 }),
)!;
expect(report.load?.keys).toEqual(['items']);
expect(resolveAnalogReport(project, report).load?.keys).toEqual(['items']);
expect(loadFrom).toBe(0);
});

it('credits the child when it resolves its own load, even to the same object', async () => {
const cached = { items: [1, 2] };
const routes: Routes = [
{
path: 'products',
children: [
analogRoute('/pages/products', '/src/app/pages/products.server.ts', {
path: '',
component: ProductsLayout,
resolve: { load: () => cached },
children: [
analogRoute('/pages/products/index', '/src/app/pages/products/index.server.ts', {
path: '',
component: ProductList,
resolve: { load: () => cached },
}),
],
}),
],
},
];
TestBed.resetTestingModule();
TestBed.configureTestingModule({ providers: [provideRouter(routes)] });
const router = TestBed.inject(Router);
await router.navigateByUrl('/products');
const { loadFrom } = chainOf(
router.routerState.snapshot.root as unknown as Record<string, any>,
);
expect(loadFrom).toBe(1);
});
});
6 changes: 5 additions & 1 deletion packages/devtools/src/analog-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,11 @@ export function chainOf(root: AnyRecord | null): {
if (meta) {
chain.push({ path: `/${segments.join('/')}`, ...fileOfEndpoint(meta.endpointKey) });
const load = read(() => node!['data']?.['load'], undefined);
if (load !== undefined && (loadFrom === undefined || !isEmptyObject(load))) {
// An empty-path child inherits its parent's resolved data, so the same
// object again is the parent's load(), unless this route resolves its own.
const own = read(() => !!config?.['resolve']?.['load'], false);
const inherited = load === data && !own;
if (load !== undefined && !inherited && (loadFrom === undefined || !isEmptyObject(load))) {
data = load;
loadFrom = chain.length - 1;
}
Expand Down
Loading