168 lines
4.8 KiB
TypeScript
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();
|