Files
dees-catalog/test/test.dees-table-liveupdates.chromium.ts

168 lines
4.8 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as deesCatalog from '../ts_web/index.js';
import type {
Column,
ISortDescriptor,
} from '../ts_web/elements/00group-dataview/dees-table/index.js';
interface ITestRow {
id: string;
score: number;
label: string;
}
const testColumns: Column<ITestRow>[] = [
{ key: 'id', header: 'ID' },
{ key: 'score', header: 'Score' },
{ key: 'label', header: 'Label' },
];
const scoreSort: ISortDescriptor[] = [{ key: 'score', dir: 'desc' }];
const waitForNextFrame = async () => {
await new Promise<void>((resolve) => {
requestAnimationFrame(() => resolve());
});
};
const waitForMacrotask = async () => {
await new Promise<void>((resolve) => {
window.setTimeout(() => resolve(), 0);
});
};
const settleTable = async (table: deesCatalog.DeesTable<ITestRow>) => {
await table.updateComplete;
await waitForNextFrame();
await waitForMacrotask();
await table.updateComplete;
};
const createRows = (iteration: number): ITestRow[] => {
const cycle = iteration % 3;
if (cycle === 0) {
return [
{ id: 'alpha', score: 60, label: `Alpha ${iteration}` },
{ id: 'beta', score: 20, label: `Beta ${iteration}` },
{ id: 'gamma', score: 40, label: `Gamma ${iteration}` },
];
}
if (cycle === 1) {
return [
{ id: 'alpha', score: 30, label: `Alpha ${iteration}` },
{ id: 'beta', score: 70, label: `Beta ${iteration}` },
{ id: 'gamma', score: 50, label: `Gamma ${iteration}` },
];
}
return [
{ id: 'alpha', score: 55, label: `Alpha ${iteration}` },
{ id: 'beta', score: 35, label: `Beta ${iteration}` },
{ id: 'gamma', score: 75, label: `Gamma ${iteration}` },
];
};
const createTable = (
rows: ITestRow[],
highlightUpdates: 'none' | 'flash'
): deesCatalog.DeesTable<ITestRow> => {
const table = new deesCatalog.DeesTable<ITestRow>();
table.searchable = false;
table.columns = testColumns;
table.rowKey = 'id';
table.sortBy = scoreSort;
table.highlightUpdates = highlightUpdates;
table.data = rows;
document.body.appendChild(table);
return table;
};
const countComments = (root: Node): number => {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
let count = 0;
while (walker.nextNode()) count++;
return count;
};
const getBodyRows = (table: deesCatalog.DeesTable<ITestRow>): HTMLTableRowElement[] =>
Array.from(
table.shadowRoot?.querySelectorAll('tbody tr[data-row-idx]') ?? []
) as HTMLTableRowElement[];
const getRenderedRowIds = (table: deesCatalog.DeesTable<ITestRow>): string[] =>
getBodyRows(table).map((row) => row.cells[0]?.textContent?.trim() ?? '');
const getRenderedRowMap = (
table: deesCatalog.DeesTable<ITestRow>
): Map<string, HTMLTableRowElement> => {
const rowMap = new Map<string, HTMLTableRowElement>();
for (const row of getBodyRows(table)) {
const rowId = row.cells[0]?.textContent?.trim() ?? '';
if (rowId) rowMap.set(rowId, row);
}
return rowMap;
};
tap.test('dees-table avoids repeated width measurement and comment growth on live updates', async () => {
const table = new deesCatalog.DeesTable<ITestRow>();
let widthMeasureCalls = 0;
const originalDetermineColumnWidths = table.determineColumnWidths.bind(table);
table.determineColumnWidths = (async () => {
widthMeasureCalls++;
await originalDetermineColumnWidths();
}) as typeof table.determineColumnWidths;
table.searchable = false;
table.columns = testColumns;
table.rowKey = 'id';
table.sortBy = scoreSort;
table.highlightUpdates = 'none';
table.data = createRows(0);
document.body.appendChild(table);
try {
await settleTable(table);
const initialWidthMeasureCalls = widthMeasureCalls;
const initialCommentCount = countComments(table.shadowRoot!);
expect(initialWidthMeasureCalls).toBeGreaterThan(0);
for (let iteration = 1; iteration <= 10; iteration++) {
table.data = createRows(iteration);
await settleTable(table);
}
expect(widthMeasureCalls).toEqual(initialWidthMeasureCalls);
expect(countComments(table.shadowRoot!)).toEqual(initialCommentCount);
} finally {
table.remove();
}
});
tap.test('dees-table reuses row DOM while flashing live-sorted updates', async () => {
const table = createTable(createRows(0), 'flash');
try {
await settleTable(table);
const initialRowMap = getRenderedRowMap(table);
table.data = createRows(1);
await settleTable(table);
const updatedRowMap = getRenderedRowMap(table);
expect(getRenderedRowIds(table)).toEqual(['beta', 'gamma', 'alpha']);
expect(updatedRowMap.get('alpha')).toEqual(initialRowMap.get('alpha'));
expect(updatedRowMap.get('beta')).toEqual(initialRowMap.get('beta'));
expect(updatedRowMap.get('gamma')).toEqual(initialRowMap.get('gamma'));
} finally {
table.remove();
}
});
export default tap.start();