mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-09-16 10:32:10 +00:00
perf(pokedex): send the box grid as packed rows with the page
The server load fetched a full combined-data page at a 9999 item page size, carrying detail text and ownership fields the grid never renders, and the client re-fetched the same payload after hydration. Load a trimmed grid row instead and pack it as positional tuples so field names are not repeated for every one of a thousand-plus entries. Entry detail is fetched on demand from the new per-entry endpoint when a cell is opened, and the grid marks itself interactive so other page-start work can queue behind it.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { afterCriticalPageWork, markGridInteractive } from '$lib/utils/criticalPageWork';
|
||||
|
||||
describe('optional work scheduling', () => {
|
||||
let events: EventTarget & Record<string, unknown>;
|
||||
let idle: (() => void) | undefined;
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
events = Object.assign(new EventTarget(), {
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
requestIdleCallback: vi.fn((callback: () => void) => {
|
||||
idle = callback;
|
||||
return 1;
|
||||
}),
|
||||
cancelIdleCallback: vi.fn()
|
||||
});
|
||||
idle = undefined;
|
||||
vi.stubGlobal('window', events);
|
||||
vi.stubGlobal('location', { pathname: '/pokedex/example' });
|
||||
vi.stubGlobal('document', { querySelector: () => null });
|
||||
vi.stubGlobal('requestAnimationFrame', (callback: () => void) => setTimeout(callback, 16));
|
||||
vi.stubGlobal('cancelAnimationFrame', clearTimeout);
|
||||
});
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('waits for interactive cells, coalesces events, then runs at idle once', () => {
|
||||
const run = vi.fn();
|
||||
afterCriticalPageWork(run);
|
||||
vi.advanceTimersByTime(1000);
|
||||
expect(run).not.toHaveBeenCalled();
|
||||
events.dispatchEvent(new Event('livingdex:grid-interactive'));
|
||||
events.dispatchEvent(new Event('livingdex:grid-interactive'));
|
||||
vi.advanceTimersByTime(16);
|
||||
expect(events.requestIdleCallback).toHaveBeenCalledTimes(1);
|
||||
expect(run).not.toHaveBeenCalled();
|
||||
idle!();
|
||||
vi.advanceTimersByTime(5000);
|
||||
expect(run).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('falls back after five seconds if no grid becomes interactive', () => {
|
||||
const run = vi.fn();
|
||||
afterCriticalPageWork(run);
|
||||
vi.advanceTimersByTime(4999);
|
||||
expect(run).not.toHaveBeenCalled();
|
||||
vi.advanceTimersByTime(1);
|
||||
expect(run).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('cancels work after navigation or an explicit refresh takes over', () => {
|
||||
const run = vi.fn();
|
||||
const cancel = afterCriticalPageWork(run);
|
||||
events.dispatchEvent(new Event('livingdex:grid-interactive'));
|
||||
vi.advanceTimersByTime(16);
|
||||
cancel();
|
||||
idle!();
|
||||
vi.advanceTimersByTime(5000);
|
||||
expect(run).not.toHaveBeenCalled();
|
||||
});
|
||||
it('does not announce a grid with no populated cells', () => {
|
||||
const listener = vi.fn();
|
||||
events.addEventListener('livingdex:grid-interactive', listener);
|
||||
markGridInteractive();
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
});
|
||||
it('schedules other pages without waiting for a grid, even without idle callbacks', () => {
|
||||
vi.stubGlobal('location', { pathname: '/backup-settings' });
|
||||
delete events.requestIdleCallback;
|
||||
const run = vi.fn();
|
||||
afterCriticalPageWork(run);
|
||||
vi.advanceTimersByTime(32);
|
||||
expect(run).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('marks populated cells once and allows work registered after hydration', () => {
|
||||
let ready = false;
|
||||
const grid = {
|
||||
setAttribute: () => {
|
||||
ready = true;
|
||||
}
|
||||
};
|
||||
const cell = { closest: () => grid };
|
||||
vi.stubGlobal('document', {
|
||||
querySelector: (selector: string) =>
|
||||
selector === '[data-entry-index]' ? cell : ready ? grid : null
|
||||
});
|
||||
const mark = vi.fn();
|
||||
vi.stubGlobal('performance', { mark });
|
||||
markGridInteractive();
|
||||
markGridInteractive();
|
||||
expect(mark).toHaveBeenCalledTimes(1);
|
||||
const run = vi.fn();
|
||||
afterCriticalPageWork(run);
|
||||
vi.advanceTimersByTime(16);
|
||||
idle!();
|
||||
expect(run).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('does not access the DOM during SSR', () => {
|
||||
vi.stubGlobal('window', undefined);
|
||||
expect(() => markGridInteractive()).not.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user