Skip to content

Commit

Permalink
Tests (#1858)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmingles committed Mar 20, 2024
1 parent 1ca0015 commit 0212185
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 67 deletions.
173 changes: 110 additions & 63 deletions packages/jsapi-components/src/useInitializeViewportData.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { act, renderHook } from '@testing-library/react-hooks';
import type { Table } from '@deephaven/jsapi-types';
import type { dh } from '@deephaven/jsapi-types';
import { generateEmptyKeyedItems } from '@deephaven/jsapi-utils';
import { KeyedItem, TestUtils } from '@deephaven/utils';
import useInitializeViewportData from './useInitializeViewportData';
Expand All @@ -11,81 +11,128 @@ function expectedInitialItems(size: number) {
return [...generateEmptyKeyedItems(0, size - 1)];
}

const tableA = TestUtils.createMockProxy<Table>({ size: 4 });
const expectedInitialA: KeyedItem<unknown>[] = expectedInitialItems(
tableA.size
const tableSize4 = TestUtils.createMockProxy<dh.Table>({ size: 4 });
const expectedInitial4: KeyedItem<unknown>[] = expectedInitialItems(
tableSize4.size
);

const tableB = TestUtils.createMockProxy<Table>({ size: 2 });
const expectedInitialB = expectedInitialItems(tableB.size);
const tableSize2 = TestUtils.createMockProxy<dh.Table>({ size: 2 });
const expectedInitial2 = expectedInitialItems(tableSize2.size);

beforeEach(() => {
TestUtils.asMock(useTableSize).mockImplementation(table => table?.size ?? 0);
});
function updatedItems(size: number): { key: string; item: string }[] {
const items: { key: string; item: string }[] = [];

it.each([null])('should safely handle no table: %s', noTable => {
const { result } = renderHook(() => useInitializeViewportData(noTable));
expect(result.current.items).toEqual([]);
});
for (let i = 0; i < size; i += 1) {
items.push({ key: `${i}`, item: `mock.item.${i}` });
}

it('should initialize a ListData object based on Table size', () => {
const { result } = renderHook(() => useInitializeViewportData(tableA));
return items;
}

expect(result.current.items).toEqual(expectedInitialA);
beforeEach(() => {
TestUtils.asMock(useTableSize).mockImplementation(table => table?.size ?? 0);
});

it('should re-initialize a ListData object if Table reference changes', () => {
const { result, rerender } = renderHook(
({ table }) => useInitializeViewportData(table),
{
initialProps: { table: tableA },
}
);

// Update an item
const updatedItem = { key: '0', item: 'mock.item' };
act(() => {
result.current.update(updatedItem.key, updatedItem);
});
describe.each([undefined, true, false])(
'reuseItemsOnTableResize: %s',
reuseItemsOnTableResize => {
it.each([null])('should safely handle no table: %s', noTable => {
const { result } = renderHook(() =>
useInitializeViewportData(noTable, reuseItemsOnTableResize)
);
expect(result.current.items).toEqual([]);
});

const expectedAfterUpdate = [updatedItem, ...expectedInitialA.slice(1)];
expect(result.current.items).toEqual(expectedAfterUpdate);
it('should initialize a ListData object based on Table size', () => {
const { result } = renderHook(() =>
useInitializeViewportData(tableSize4, reuseItemsOnTableResize)
);

// Re-render with a new table instance
rerender({ table: tableB });
expect(result.current.items).toEqual(expectedInitial4);
});

const expectedAfterRerender = expectedInitialB;
expect(result.current.items).toEqual(expectedAfterRerender);
});
it('should re-initialize a ListData object if Table reference changes', () => {
const { result, rerender } = renderHook(
({ table }) =>
useInitializeViewportData(table, reuseItemsOnTableResize),
{
initialProps: { table: tableSize4 },
}
);

const updatedItems4 = updatedItems(tableSize4.size);

// Update items
act(() => {
updatedItems4.forEach(updatedItem => {
result.current.update(updatedItem.key, updatedItem);
});
});

expect(result.current.items).toEqual(updatedItems4);

// Re-render with a smaller table instance
rerender({ table: tableSize2 });

expect(result.current.items).toEqual(
reuseItemsOnTableResize === true
? updatedItems4.slice(0, tableSize2.size)
: expectedInitial2
);

// Re-render with a larger table instance
rerender({ table: tableSize4 });

expect(result.current.items).toEqual(
reuseItemsOnTableResize === true
? [
...updatedItems4.slice(0, tableSize2.size),
...expectedInitial4.slice(tableSize2.size),
]
: expectedInitial4
);
});

it.each([
[3, expectedInitialItems(3)],
[5, expectedInitialItems(5)],
])(
'should re-initialize a ListData object if Table size changes: %s, %s',
(newSize, expectedAfterSizeChange) => {
const { result, rerender } = renderHook(
({ table }) => useInitializeViewportData(table),
{
initialProps: { table: tableA },
it.each([3, 5])(
'should re-initialize a ListData object if Table size changes: %s',
newSize => {
const { result, rerender } = renderHook(
({ table }) =>
useInitializeViewportData(table, reuseItemsOnTableResize),
{
initialProps: { table: tableSize4 },
}
);

expect(result.current.items).toEqual(expectedInitial4);

const updatedItems4 = updatedItems(tableSize4.size);

// Update items
act(() => {
updatedItems4.forEach(updatedItem => {
result.current.update(updatedItem.key, updatedItem);
});
});

expect(result.current.items).toEqual(updatedItems4);

// Re-render with new size
TestUtils.asMock(useTableSize).mockImplementation(() => newSize);
rerender({
table:
tableSize4 /* this table is not actually used due to mocking `useTableSize` above */,
});

expect(result.current.items).toEqual(
reuseItemsOnTableResize === true
? [
...updatedItems4.slice(0, newSize),
...expectedInitialItems(newSize).slice(4),
]
: expectedInitialItems(newSize)
);
}
);

expect(result.current.items).toEqual(expectedInitialA);

// Update an item
const updatedItem = { key: '0', item: 'mock.item' };
act(() => {
result.current.update(updatedItem.key, updatedItem);
});

const expectedAfterUpdate = [updatedItem, ...expectedInitialA.slice(1)];
expect(result.current.items).toEqual(expectedAfterUpdate);

// Re-render with new size
TestUtils.asMock(useTableSize).mockImplementation(() => newSize);
rerender({ table: tableA });

expect(result.current.items).toEqual(expectedAfterSizeChange);
}
);
16 changes: 12 additions & 4 deletions packages/jsapi-components/src/useInitializeViewportData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function resizeItemsArray<T>({

// Drop extra items
if (currentSize > targetSize) {
return items.slice(0, targetSize - 1);
return items.slice(0, targetSize);
}

// Add missing items
Expand All @@ -62,9 +62,17 @@ function resizeItemsArray<T>({
* source table. This is intended for "human" sized tables such as those used in
* admin panels. This is not suitable for "machine" scale with millions+ rows.
* @param table The table that will be used to determine the list size.
* @param reuseItemsOnTableResize If true, the items will be reused when the
* table resizes. Defaults to false which will replace the items when the table
* resizes.
* @param reuseItemsOnTableResize Whether to reuse existing items when the table
* size changes (defaults to false).
* - If true, existing items will be reused when the table resizes. This is
* recommended for ticking tables where the data is frequently updated in order
* to avoid UI flicker.
* - If false, all of the items will be replaced when the table resizes. This is
* recommnded for tables that don't change size frequently but may change size
* due to a user interaction. e.g. Filter via a search input. This avoids a
* different kind of flicker, where the item values will shift around as the
* user types. It is less jarring for the items to all reset to empty and then
* show the new results all at once.
* @returns a WindowedListData object.
*/
export function useInitializeViewportData<T>(
Expand Down

0 comments on commit 0212185

Please sign in to comment.