From 1365d7d8f08a45acc171b73e2c29654765563bdf Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Thu, 3 Apr 2025 14:34:12 -0700 Subject: [PATCH] Add wrapResolved to be a faster version of wrapPromise --- .../HighTable/HighTable.stories.tsx | 8 ++--- src/helpers/dataframe.ts | 6 ++-- src/utils/promise.ts | 21 ++++++++----- test/components/HighTable/HighTable.test.tsx | 30 +++++++++---------- test/helpers/dataframe.test.ts | 6 ++-- test/helpers/row.test.ts | 6 ++-- test/helpers/rowCache.test.ts | 6 ++-- test/helpers/selection.test.ts | 6 ++-- 8 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/components/HighTable/HighTable.stories.tsx b/src/components/HighTable/HighTable.stories.tsx index f0af61cf..bfec055a 100644 --- a/src/components/HighTable/HighTable.stories.tsx +++ b/src/components/HighTable/HighTable.stories.tsx @@ -1,16 +1,16 @@ import type { Meta, StoryObj } from '@storybook/react' import { DataFrame } from '../../helpers/dataframe.js' -import { wrapPromise } from '../../utils/promise.js' +import { wrapResolved } from '../../utils/promise.js' import HighTable from './HighTable.js' const data: DataFrame = { header: ['ID', 'Count'], numRows: 1000, rows: ({ start, end }) => Array.from({ length: end - start }, (_, index) => ({ - index: wrapPromise(index + start), + index: wrapResolved(index + start), cells: { - ID: wrapPromise(`row ${index + start}`), - Count: wrapPromise(1000 - start - index), + ID: wrapResolved(`row ${index + start}`), + Count: wrapResolved(1000 - start - index), }, })), } diff --git a/src/helpers/dataframe.ts b/src/helpers/dataframe.ts index 7b0af898..ed99af10 100644 --- a/src/helpers/dataframe.ts +++ b/src/helpers/dataframe.ts @@ -1,4 +1,4 @@ -import { wrapPromise } from '../utils/promise.js' +import { wrapResolved } from '../utils/promise.js' import { AsyncRow, Cells, asyncRows } from './row.js' import { OrderBy } from './sort.js' @@ -186,8 +186,8 @@ export function arrayDataFrame(data: Cells[]): DataFrame { numRows: data.length, rows({ start, end }): AsyncRow[] { return data.slice(start, end).map((cells, i) => ({ - index: wrapPromise(start + i), - cells: Object.fromEntries(Object.entries(cells).map(([key, value]) => [key, wrapPromise(value)])), + index: wrapResolved(start + i), + cells: Object.fromEntries(Object.entries(cells).map(([key, value]) => [key, wrapResolved(value)])), })) }, getColumn({ column, start = 0, end = data.length }): Promise { diff --git a/src/utils/promise.ts b/src/utils/promise.ts index 147becfb..daea6c87 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -3,14 +3,16 @@ export type WrappedPromise = Promise & { rejected?: any } +export type ResolvablePromise = Promise & { + resolve: (value: T) => void + reject: (reason?: any) => void +} + /** * Wrap a promise to save the resolved value and error. * Note: you can't await on a WrappedPromise, you must use then. */ -export function wrapPromise(promise: Promise | T): WrappedPromise { - if (!(promise instanceof Promise)) { - promise = Promise.resolve(promise) - } +export function wrapPromise(promise: Promise): WrappedPromise { const wrapped: WrappedPromise = promise.then(resolved => { wrapped.resolved = resolved return resolved @@ -21,9 +23,14 @@ export function wrapPromise(promise: Promise | T): WrappedPromise { return wrapped } -export type ResolvablePromise = Promise & { - resolve: (value: T) => void - reject: (reason?: any) => void +/** + * Similar to `wrapPromise`, but for a resolved value. + * Returns immediately without creating a microtask. + */ +export function wrapResolved(value: T): WrappedPromise { + const wrapped: WrappedPromise = Promise.resolve(value) as WrappedPromise + wrapped.resolved = value + return wrapped } /** diff --git a/test/components/HighTable/HighTable.test.tsx b/test/components/HighTable/HighTable.test.tsx index bbc1f666..56d645da 100644 --- a/test/components/HighTable/HighTable.test.tsx +++ b/test/components/HighTable/HighTable.test.tsx @@ -3,17 +3,17 @@ import React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' import HighTable from '../../../src/components/HighTable/HighTable.js' import { DataFrame, sortableDataFrame } from '../../../src/helpers/dataframe.js' -import { wrapPromise } from '../../../src/utils/promise.js' +import { wrapResolved } from '../../../src/utils/promise.js' import { render } from '../../userEvent.js' const data: DataFrame = { header: ['ID', 'Count'], numRows: 1000, rows: ({ start, end }) => Array.from({ length: end - start }, (_, index) => ({ - index: wrapPromise(index + start), + index: wrapResolved(index + start), cells: { - ID: wrapPromise(`row ${index + start}`), - Count: wrapPromise(1000 - start - index), + ID: wrapResolved(`row ${index + start}`), + Count: wrapResolved(1000 - start - index), }, })), } @@ -22,10 +22,10 @@ const otherData: DataFrame = { header: ['ID', 'Count'], numRows: 1000, rows: ({ start, end }) => Array.from({ length: end - start }, (_, index) => ({ - index: wrapPromise(index + start), + index: wrapResolved(index + start), cells: { - ID: wrapPromise(`other ${index + start}`), - Count: wrapPromise(1000 - start - index), + ID: wrapResolved(`other ${index + start}`), + Count: wrapResolved(1000 - start - index), }, })), } @@ -35,11 +35,11 @@ describe('HighTable', () => { header: ['ID', 'Name', 'Age'], numRows: 100, rows: vi.fn(({ start, end }: { start: number, end: number }) => Array.from({ length: end - start }, (_, index) => ({ - index: wrapPromise(index + start), + index: wrapResolved(index + start), cells: { - ID: wrapPromise(index + start), - Name: wrapPromise(`Name ${index + start}`), - Age: wrapPromise(20 + index % 50), + ID: wrapResolved(index + start), + Name: wrapResolved(`Name ${index + start}`), + Age: wrapResolved(20 + index % 50), }, })) ), @@ -67,11 +67,9 @@ describe('HighTable', () => { }) }) - it('creates the rows after having fetched the data', async () => { - const { findByRole, queryByRole } = render() - expect(queryByRole('cell', { name: 'Name 0' })).toBeNull() - // await because we have to wait for the data to be fetched first - await findByRole('cell', { name: 'Name 0' }) + it('creates the rows after having fetched the data', () => { + const { queryByRole } = render() + expect(queryByRole('cell', { name: 'Name 0' })).toBeDefined() }) it('handles scroll to load more rows', async () => { diff --git a/test/helpers/dataframe.test.ts b/test/helpers/dataframe.test.ts index fbcd37ef..218eddb5 100644 --- a/test/helpers/dataframe.test.ts +++ b/test/helpers/dataframe.test.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from 'vitest' import { DataFrame, arrayDataFrame, getGetColumn, getRanks, sortableDataFrame } from '../../src/helpers/dataframe.js' import { AsyncRow, Row, awaitRows } from '../../src/helpers/row.js' -import { wrapPromise } from '../../src/utils/promise.js' +import { wrapResolved } from '../../src/utils/promise.js' export function wrapObject({ index, cells }: Row): AsyncRow { return { - index: wrapPromise(index), + index: wrapResolved(index), cells: Object.fromEntries( - Object.entries(cells).map(([key, value]) => [key, wrapPromise(value)]) + Object.entries(cells).map(([key, value]) => [key, wrapResolved(value)]) ), } } diff --git a/test/helpers/row.test.ts b/test/helpers/row.test.ts index a1913ba2..173a044a 100644 --- a/test/helpers/row.test.ts +++ b/test/helpers/row.test.ts @@ -1,12 +1,12 @@ import { describe, expect, it } from 'vitest' import { AsyncRow, Row, awaitRows } from '../../src/helpers/row.js' -import { wrapPromise } from '../../src/utils/promise.js' +import { wrapResolved } from '../../src/utils/promise.js' export function wrapObject({ index, cells }: Row): AsyncRow { return { - index: wrapPromise(index), + index: wrapResolved(index), cells: Object.fromEntries( - Object.entries(cells).map(([key, value]) => [key, wrapPromise(value)]) + Object.entries(cells).map(([key, value]) => [key, wrapResolved(value)]) ), } } diff --git a/test/helpers/rowCache.test.ts b/test/helpers/rowCache.test.ts index e0d09319..b29617a9 100644 --- a/test/helpers/rowCache.test.ts +++ b/test/helpers/rowCache.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { AsyncRow, awaitRows } from '../../src/helpers/row.js' import { rowCache } from '../../src/helpers/rowCache.js' -import { wrapPromise } from '../../src/utils/promise.js' +import { wrapResolved } from '../../src/utils/promise.js' // Mock DataFrame function makeDf() { @@ -12,9 +12,9 @@ function makeDf() { new Array(end - start) .fill(null) .map((_, index) => ({ - index: wrapPromise(start + index), + index: wrapResolved(start + index), cells: { - id: wrapPromise(start + index), + id: wrapResolved(start + index), }, })) ), diff --git a/test/helpers/selection.test.ts b/test/helpers/selection.test.ts index b50edc39..40d81ed1 100644 --- a/test/helpers/selection.test.ts +++ b/test/helpers/selection.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, test, vi } from 'vitest' import { DataFrame, sortableDataFrame } from '../../src/helpers/dataframe.js' import { AsyncRow, Row } from '../../src/helpers/row.js' import { areAllSelected, areValidRanges, convertSelection, extendFromAnchor, invertPermutationIndexes, isSelected, isValidIndex, isValidRange, selectRange, toggleAll, toggleIndex, toggleIndexInSelection, toggleRangeInSelection, toggleRangeInTable, unselectRange } from '../../src/helpers/selection.js' -import { wrapPromise } from '../../src/utils/promise.js' +import { wrapResolved } from '../../src/utils/promise.js' describe('an index', () => { test('is a positive integer', () => { @@ -221,9 +221,9 @@ const data = [ export function wrapObject({ index, cells }: Row): AsyncRow { return { - index: wrapPromise(index), + index: wrapResolved(index), cells: Object.fromEntries( - Object.entries(cells).map(([key, value]) => [key, wrapPromise(value)]) + Object.entries(cells).map(([key, value]) => [key, wrapResolved(value)]) ), } }