From 1c4dac0a617f445ed737c38e1dab9691ccbdb71d Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 01:55:56 -0500 Subject: [PATCH 01/13] feat(benchmarks): add TIER_1_OPTIONS and TIER_2_OPTIONS presets --- packages/benchmarks/src/utils/bench-options.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/benchmarks/src/utils/bench-options.ts b/packages/benchmarks/src/utils/bench-options.ts index 71f9374..f5495b7 100644 --- a/packages/benchmarks/src/utils/bench-options.ts +++ b/packages/benchmarks/src/utils/bench-options.ts @@ -13,3 +13,15 @@ const DEFAULT_OPTIONS: BenchRenderOptions = { export function resolveOptions(options?: BenchRenderOptions): BenchRenderOptions { return { ...DEFAULT_OPTIONS, ...options }; } + +/** Tier 1: deep benchmarks (mount + rerender + mountMany). */ +export const TIER_1_OPTIONS: BenchRenderOptions = { + iterations: 100, + warmupIterations: 5, +}; + +/** Tier 2: light benchmarks (mount + rerender only). */ +export const TIER_2_OPTIONS: BenchRenderOptions = { + iterations: 50, + warmupIterations: 3, +}; From a65898bb626a080105dbce74c289ffc7506f701b Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 01:57:21 -0500 Subject: [PATCH 02/13] feat(benchmarks): add shared data factories for benchmark tests --- packages/benchmarks/src/utils/factories.ts | 113 +++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 packages/benchmarks/src/utils/factories.ts diff --git a/packages/benchmarks/src/utils/factories.ts b/packages/benchmarks/src/utils/factories.ts new file mode 100644 index 0000000..fa38261 --- /dev/null +++ b/packages/benchmarks/src/utils/factories.ts @@ -0,0 +1,113 @@ +/** + * Shared data factories for benchmark tests. + * Pre-generate data outside the benchmark loop so construction cost isn't measured. + */ + +// ── Row data (DataTable, BasicList, SelectableList, RowList) ── + +export interface Row { + id: number; + name: string; + status: string; + value: number; +} + +export function makeRows(count: number): Row[] { + return Array.from({ length: count }, (_, i) => ({ + id: i, + name: `Row ${i}`, + status: i % 3 === 0 ? 'active' : i % 3 === 1 ? 'pending' : 'inactive', + value: (i * 7 + 13) % 1000, + })); +} + +// ── Tree node data (TreeList) ── + +export interface TreeNode { + id: string; + label: string; + children?: TreeNode[]; +} + +export function makeTreeNodes(count: number, depth: number = 2): TreeNode[] { + function build(prefix: string, remaining: number): TreeNode[] { + return Array.from({ length: count }, (_, i) => ({ + id: `${prefix}-${i}`, + label: `Node ${prefix}-${i}`, + children: remaining > 0 ? build(`${prefix}-${i}`, remaining - 1) : undefined, + })); + } + return build('root', depth); +} + +// ── Option data (Select, Autocomplete, Combobox, MultiSelect) ── + +export interface Option { + value: string; + label: string; +} + +export function makeOptions(count: number): Option[] { + return Array.from({ length: count }, (_, i) => ({ + value: `option-${i}`, + label: `Option ${i}`, + })); +} + +// ── Column definitions (DataTable, RowList) ── + +export interface Column { + id: string; + header: string; + accessorKey: keyof T; +} + +export function makeColumns(): Column[] { + return [ + { id: 'id', header: 'ID', accessorKey: 'id' }, + { id: 'name', header: 'Name', accessorKey: 'name' }, + { id: 'status', header: 'Status', accessorKey: 'status' }, + { id: 'value', header: 'Value', accessorKey: 'value' }, + ]; +} + +// ── Tag data (TagInput, FilterBar) ── + +export function makeTags(count: number): string[] { + return Array.from({ length: count }, (_, i) => `tag-${i}`); +} + +// ── Command items (CommandList) ── + +export interface CommandItem { + id: string; + label: string; + group?: string; + keywords?: string[]; +} + +export function makeCommandItems(count: number): CommandItem[] { + const groups = ['File', 'Edit', 'View', 'Navigate', 'Run']; + return Array.from({ length: count }, (_, i) => ({ + id: `cmd-${i}`, + label: `Command ${i}`, + group: groups[i % groups.length], + keywords: [`keyword-${i}`], + })); +} + +// ── Tab data (EditorTabs) ── + +export interface TabItem { + id: string; + label: string; + closable?: boolean; +} + +export function makeTabs(count: number): TabItem[] { + return Array.from({ length: count }, (_, i) => ({ + id: `tab-${i}`, + label: `Tab ${i}`, + closable: true, + })); +} From 2500b2e8967c78f545e7c4146ed941def1f1306b Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 01:59:39 -0500 Subject: [PATCH 03/13] feat(benchmarks): add ResizeObserver and IntersectionObserver jsdom stubs --- packages/benchmarks/src/setup.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/benchmarks/src/setup.ts b/packages/benchmarks/src/setup.ts index bb02c60..f1731ac 100644 --- a/packages/benchmarks/src/setup.ts +++ b/packages/benchmarks/src/setup.ts @@ -1 +1,24 @@ import '@testing-library/jest-dom/vitest'; + +// ── Browser API stubs for jsdom ── +// Components like ResizableSplitPane, DockLayout, ScrollArea depend on these. + +if (typeof globalThis.ResizeObserver === 'undefined') { + globalThis.ResizeObserver = class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} + } as unknown as typeof ResizeObserver; +} + +if (typeof globalThis.IntersectionObserver === 'undefined') { + globalThis.IntersectionObserver = class IntersectionObserver { + readonly root = null; + readonly rootMargin = '0px'; + readonly thresholds = [0]; + observe() {} + unobserve() {} + disconnect() {} + takeRecords() { return []; } + } as unknown as typeof IntersectionObserver; +} From 29ec28d4f44b17c6773687a812a1c85ef2d537a9 Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 01:59:58 -0500 Subject: [PATCH 04/13] refactor(benchmarks): update existing benchmarks to use TIER_2_OPTIONS --- packages/benchmarks/src/base-ui/Box.bench.tsx | 9 ++++----- packages/benchmarks/src/base-ui/Button.bench.tsx | 11 +++++------ packages/benchmarks/src/base-ui/Checkbox.bench.tsx | 8 ++++---- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/benchmarks/src/base-ui/Box.bench.tsx b/packages/benchmarks/src/base-ui/Box.bench.tsx index 3294ad9..a694ffa 100644 --- a/packages/benchmarks/src/base-ui/Box.bench.tsx +++ b/packages/benchmarks/src/base-ui/Box.bench.tsx @@ -1,11 +1,10 @@ import { describe } from 'vitest'; import { benchRender, benchMountMany } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Box } from '@omniview/base-ui'; describe('Box', () => { - benchRender('mount div', () => Content); - - benchRender('mount section', () => Content); - - benchMountMany('mount 1000', 1000, (i) => Item {i}); + benchRender('mount div', () => Content, TIER_2_OPTIONS); + benchRender('mount section', () => Content, TIER_2_OPTIONS); + benchMountMany('mount 1000', 1000, (i) => Item {i}, TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Button.bench.tsx b/packages/benchmarks/src/base-ui/Button.bench.tsx index 36c78fb..6874c1a 100644 --- a/packages/benchmarks/src/base-ui/Button.bench.tsx +++ b/packages/benchmarks/src/base-ui/Button.bench.tsx @@ -1,21 +1,20 @@ import { describe } from 'vitest'; import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Button } from '@omniview/base-ui'; describe('Button', () => { - benchRender('mount', () => ); - + benchRender('mount', () => , TIER_2_OPTIONS); benchRender('mount with decorators', () => ( - )); - + ), TIER_2_OPTIONS); benchRerender( 'variant change', { initialProps: { variant: 'solid' as const }, updatedProps: { variant: 'outline' as const } }, (props) => , + TIER_2_OPTIONS, ); - - benchMountMany('mount 1000', 1000, (i) => ); + benchMountMany('mount 1000', 1000, (i) => , TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Checkbox.bench.tsx b/packages/benchmarks/src/base-ui/Checkbox.bench.tsx index 5786a9a..2814734 100644 --- a/packages/benchmarks/src/base-ui/Checkbox.bench.tsx +++ b/packages/benchmarks/src/base-ui/Checkbox.bench.tsx @@ -1,15 +1,15 @@ import { describe } from 'vitest'; import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Checkbox } from '@omniview/base-ui'; describe('Checkbox', () => { - benchRender('mount', () => ); - + benchRender('mount', () => , TIER_2_OPTIONS); benchRerender( 'checked toggle', { initialProps: { checked: false }, updatedProps: { checked: true } }, (props) => , + TIER_2_OPTIONS, ); - - benchMountMany('mount 1000', 1000, (i) => ); + benchMountMany('mount 1000', 1000, (i) => , TIER_2_OPTIONS); }); From 341c2158e49f0ba044049c75ded24a862c528d5f Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 02:00:31 -0500 Subject: [PATCH 05/13] feat(benchmarks): retrofit DataTable with rerender + mountMany benchmarks --- .../src/base-ui/DataTable.bench.tsx | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/packages/benchmarks/src/base-ui/DataTable.bench.tsx b/packages/benchmarks/src/base-ui/DataTable.bench.tsx index a3a3a87..b422628 100644 --- a/packages/benchmarks/src/base-ui/DataTable.bench.tsx +++ b/packages/benchmarks/src/base-ui/DataTable.bench.tsx @@ -6,14 +6,9 @@ import { type ColumnDef, } from '@tanstack/react-table'; import { DataTable } from '@omniview/base-ui'; -import { benchRender } from '../utils/bench-render'; - -interface Row { - id: number; - name: string; - status: string; - value: number; -} +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeRows, type Row } from '../utils/factories'; const columnHelper = createColumnHelper(); @@ -24,23 +19,10 @@ const columns: ColumnDef[] = [ columnHelper.accessor('value', { header: 'Value' }), ]; -function generateRows(count: number): Row[] { - return Array.from({ length: count }, (_, i) => ({ - id: i, - name: `Row ${i}`, - status: i % 3 === 0 ? 'active' : i % 3 === 1 ? 'pending' : 'inactive', - value: (i * 7 + 13) % 1000, - })); -} - -// Pre-generate data so row construction isn't measured -const rows100 = generateRows(100); -const rows1000 = generateRows(1000); +const rows100 = makeRows(100); +const rows500 = makeRows(500); +const rows600 = makeRows(600); -/** - * Wrapper component that calls useReactTable internally and renders - * DataTable with its compound children. - */ function DataTableBench({ data }: { data: Row[] }) { const table = useReactTable({ data, @@ -59,6 +41,20 @@ function DataTableBench({ data }: { data: Row[] }) { } describe('DataTable', () => { - benchRender('mount 100 rows', () => ); - benchRender('mount 1000 rows', () => ); + benchRender('mount 100 rows', () => , TIER_1_OPTIONS); + benchRender('mount 500 rows', () => , TIER_1_OPTIONS); + + benchRerender( + 'data change (500 → 600 rows)', + { initialProps: { data: rows500 }, updatedProps: { data: rows600 } }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 50 tables (10 rows each)', + 50, + (i) => , + TIER_1_OPTIONS, + ); }); From 399871cb5dae4879634909b04a9797fdda722deb Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 02:19:42 -0500 Subject: [PATCH 06/13] feat(benchmarks): add Tier 1 data-heavy component benchmarks Add mount, rerender, and mountMany benchmarks for TreeList, CommandList, BasicList, SelectableList, and RowList components. --- .../src/base-ui/BasicList.bench.tsx | 51 ++++++++++ .../src/base-ui/CommandList.bench.tsx | 62 ++++++++++++ .../benchmarks/src/base-ui/RowList.bench.tsx | 66 +++++++++++++ .../src/base-ui/SelectableList.bench.tsx | 68 +++++++++++++ .../benchmarks/src/base-ui/TreeList.bench.tsx | 99 +++++++++++++++++++ 5 files changed, 346 insertions(+) create mode 100644 packages/benchmarks/src/base-ui/BasicList.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/CommandList.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/RowList.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/SelectableList.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/TreeList.bench.tsx diff --git a/packages/benchmarks/src/base-ui/BasicList.bench.tsx b/packages/benchmarks/src/base-ui/BasicList.bench.tsx new file mode 100644 index 0000000..60a7572 --- /dev/null +++ b/packages/benchmarks/src/base-ui/BasicList.bench.tsx @@ -0,0 +1,51 @@ +import { describe } from 'vitest'; +import { BasicList } from '@omniview/base-ui'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeRows, type Row } from '../utils/factories'; + +// Pre-generate data +const rows500 = makeRows(500); +const rows600 = makeRows(600); +const rows25 = makeRows(25); + +// --------------------------------------------------------------------------- +// Wrapper +// --------------------------------------------------------------------------- + +function BasicListBench({ data }: { data: Row[] }) { + return ( + + + {data.map((row) => ( + + {row.name} + {row.status} + + ))} + + + ); +} + +// --------------------------------------------------------------------------- +// Benchmarks +// --------------------------------------------------------------------------- + +describe('BasicList', () => { + benchRender('mount 500 items', () => , TIER_1_OPTIONS); + + benchRerender( + 'data change (500 -> 600 items)', + { initialProps: { data: rows500 }, updatedProps: { data: rows600 } }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 20 instances (25 items each)', + 20, + (i) => , + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/CommandList.bench.tsx b/packages/benchmarks/src/base-ui/CommandList.bench.tsx new file mode 100644 index 0000000..5424ca3 --- /dev/null +++ b/packages/benchmarks/src/base-ui/CommandList.bench.tsx @@ -0,0 +1,62 @@ +import { describe } from 'vitest'; +import { CommandList } from '@omniview/base-ui'; +import type { CommandItemMeta } from '@omniview/base-ui'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeCommandItems, type CommandItem } from '../utils/factories'; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +const itemKey = (item: CommandItem) => item.id; +const getTextValue = (item: CommandItem) => item.label; +const renderItem = (item: CommandItem, meta: CommandItemMeta) => ( + + {item.label} + +); + +// Pre-generate data +const items500 = makeCommandItems(500); +const items600 = makeCommandItems(600); +const items25 = makeCommandItems(25); + +// --------------------------------------------------------------------------- +// Wrapper +// --------------------------------------------------------------------------- + +function CommandListBench({ items }: { items: CommandItem[] }) { + return ( + + + + ); +} + +// --------------------------------------------------------------------------- +// Benchmarks +// --------------------------------------------------------------------------- + +describe('CommandList', () => { + benchRender('mount 500 items', () => , TIER_1_OPTIONS); + + benchRerender( + 'data change (500 -> 600 items)', + { initialProps: { items: items500 }, updatedProps: { items: items600 } }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 20 instances (25 items each)', + 20, + (i) => , + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/RowList.bench.tsx b/packages/benchmarks/src/base-ui/RowList.bench.tsx new file mode 100644 index 0000000..4f4d9e3 --- /dev/null +++ b/packages/benchmarks/src/base-ui/RowList.bench.tsx @@ -0,0 +1,66 @@ +import { describe } from 'vitest'; +import { RowList } from '@omniview/base-ui'; +import type { ColumnDef } from '@omniview/base-ui'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeRows, type Row } from '../utils/factories'; + +// --------------------------------------------------------------------------- +// Column definitions +// --------------------------------------------------------------------------- + +const columns: ColumnDef[] = [ + { id: 'id', header: 'ID', width: '60px' }, + { id: 'name', header: 'Name', width: '1fr' }, + { id: 'status', header: 'Status', width: '100px' }, + { id: 'value', header: 'Value', width: '80px', align: 'end' }, +]; + +// Pre-generate data +const rows500 = makeRows(500); +const rows600 = makeRows(600); +const rows25 = makeRows(25); + +// --------------------------------------------------------------------------- +// Wrapper +// --------------------------------------------------------------------------- + +function RowListBench({ data }: { data: Row[] }) { + return ( + + + + {data.map((row) => ( + + {row.id} + {row.name} + {row.status} + {row.value} + + ))} + + + ); +} + +// --------------------------------------------------------------------------- +// Benchmarks +// --------------------------------------------------------------------------- + +describe('RowList', () => { + benchRender('mount 500 rows', () => , TIER_1_OPTIONS); + + benchRerender( + 'data change (500 -> 600 rows)', + { initialProps: { data: rows500 }, updatedProps: { data: rows600 } }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 20 instances (25 rows each)', + 20, + (i) => , + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/SelectableList.bench.tsx b/packages/benchmarks/src/base-ui/SelectableList.bench.tsx new file mode 100644 index 0000000..190fbeb --- /dev/null +++ b/packages/benchmarks/src/base-ui/SelectableList.bench.tsx @@ -0,0 +1,68 @@ +import { describe } from 'vitest'; +import { SelectableList } from '@omniview/base-ui'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeRows, type Row } from '../utils/factories'; + +// Pre-generate data +const rows500 = makeRows(500); +const rows25 = makeRows(25); + +// Pre-compute selection sets +const noSelection = new Set(); +const fiftySelected = new Set(Array.from({ length: 50 }, (_, i) => i)); + +// --------------------------------------------------------------------------- +// Wrapper +// --------------------------------------------------------------------------- + +function SelectableListBench({ + data, + selectedKeys, +}: { + data: Row[]; + selectedKeys?: ReadonlySet; +}) { + return ( + + + {data.map((row) => ( + + + {row.name} + {row.status} + + ))} + + + ); +} + +// --------------------------------------------------------------------------- +// Benchmarks +// --------------------------------------------------------------------------- + +describe('SelectableList', () => { + benchRender( + 'mount 500 items', + () => , + TIER_1_OPTIONS, + ); + + benchRerender( + 'selection change (0 -> 50 selected)', + { + initialProps: { data: rows500, selectedKeys: noSelection }, + updatedProps: { data: rows500, selectedKeys: fiftySelected }, + }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 20 instances (25 items each)', + 20, + (i) => , + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/TreeList.bench.tsx b/packages/benchmarks/src/base-ui/TreeList.bench.tsx new file mode 100644 index 0000000..2fec28d --- /dev/null +++ b/packages/benchmarks/src/base-ui/TreeList.bench.tsx @@ -0,0 +1,99 @@ +import { describe } from 'vitest'; +import { TreeList } from '@omniview/base-ui'; +import type { TreeNodeMeta } from '@omniview/base-ui'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; + +// --------------------------------------------------------------------------- +// Data factories +// --------------------------------------------------------------------------- + +interface TreeItem { + id: string; + label: string; + children?: TreeItem[]; +} + +function makeFlatItems(count: number): TreeItem[] { + return Array.from({ length: count }, (_, i) => ({ + id: `item-${i}`, + label: `Item ${i}`, + })); +} + +function makeNestedItems(parents: number, childrenPerParent: number): TreeItem[] { + return Array.from({ length: parents }, (_, i) => ({ + id: `parent-${i}`, + label: `Parent ${i}`, + children: Array.from({ length: childrenPerParent }, (_, j) => ({ + id: `parent-${i}-child-${j}`, + label: `Child ${i}-${j}`, + })), + })); +} + +const itemKey = (item: TreeItem) => item.id; +const getChildren = (item: TreeItem) => item.children ?? []; +const isBranch = (item: TreeItem) => (item.children?.length ?? 0) > 0; +const getTextValue = (item: TreeItem) => item.label; +const renderItem = (item: TreeItem, node: TreeNodeMeta) => ( + + + {node.isBranch && } + {item.label} + +); + +// Pre-generate data +const flat500 = makeFlatItems(500); +const nested50x2 = makeNestedItems(50, 2); +const flat100 = makeFlatItems(100); +const flat150 = makeFlatItems(150); +const flat25 = makeFlatItems(25); + +// --------------------------------------------------------------------------- +// Wrapper +// --------------------------------------------------------------------------- + +function TreeListBench({ items }: { items: TreeItem[] }) { + return ( + + + + ); +} + +// --------------------------------------------------------------------------- +// Benchmarks +// --------------------------------------------------------------------------- + +describe('TreeList', () => { + benchRender('mount 500 flat nodes', () => , TIER_1_OPTIONS); + + benchRender( + 'mount nested (50 parents x 2 children)', + () => , + TIER_1_OPTIONS, + ); + + benchRerender( + 'data change (100 -> 150 nodes)', + { initialProps: { items: flat100 }, updatedProps: { items: flat150 } }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 20 instances (25 nodes each)', + 20, + (i) => , + TIER_1_OPTIONS, + ); +}); From c3270f84b22b5553cb9e820d9d8a99dfcebbe82a Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 02:23:57 -0500 Subject: [PATCH 07/13] feat(benchmarks): add Tier 1 complex interactive benchmarks --- .../src/base-ui/Autocomplete.bench.tsx | 51 +++++++++ .../benchmarks/src/base-ui/Combobox.bench.tsx | 55 ++++++++++ .../src/base-ui/ContextMenu.bench.tsx | 75 +++++++++++++ .../benchmarks/src/base-ui/Dialog.bench.tsx | 53 +++++++++ .../src/base-ui/DockLayout.bench.tsx | 101 ++++++++++++++++++ .../benchmarks/src/base-ui/Drawer.bench.tsx | 43 ++++++++ .../src/base-ui/EditorTabs.bench.tsx | 43 ++++++++ .../src/base-ui/FilterBar.bench.tsx | 44 ++++++++ .../src/base-ui/MultiSelect.bench.tsx | 47 ++++++++ .../benchmarks/src/base-ui/Popover.bench.tsx | 65 +++++++++++ .../src/base-ui/ResizableSplitPane.bench.tsx | 41 +++++++ .../benchmarks/src/base-ui/Sheet.bench.tsx | 42 ++++++++ .../benchmarks/src/base-ui/TagInput.bench.tsx | 37 +++++++ 13 files changed, 697 insertions(+) create mode 100644 packages/benchmarks/src/base-ui/Autocomplete.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Combobox.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/ContextMenu.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Dialog.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/DockLayout.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Drawer.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/EditorTabs.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/FilterBar.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/MultiSelect.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Popover.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/ResizableSplitPane.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Sheet.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/TagInput.bench.tsx diff --git a/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx b/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx new file mode 100644 index 0000000..2d126d6 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx @@ -0,0 +1,51 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeOptions, type Option } from '../utils/factories'; +import { Autocomplete } from '@omniview/base-ui'; + +const options100 = makeOptions(100); +const options200 = makeOptions(200); +const options10 = makeOptions(10); + +function AutocompleteBench({ options }: { options: Option[] }) { + return ( + + + + + + + {(item: Option) => ( + + {item.label} + + )} + + + + + ); +} + +describe('Autocomplete', () => { + benchRender( + 'mount with 100 options', + () => , + TIER_1_OPTIONS, + ); + + benchRerender( + 'options change (100 → 200)', + { initialProps: { options: options100 }, updatedProps: { options: options200 } }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 100 instances (10 options each)', + 100, + (i) => , + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Combobox.bench.tsx b/packages/benchmarks/src/base-ui/Combobox.bench.tsx new file mode 100644 index 0000000..3e49011 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Combobox.bench.tsx @@ -0,0 +1,55 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeOptions, type Option } from '../utils/factories'; +import { Combobox } from '@omniview/base-ui'; + +const options100 = makeOptions(100); +const options200 = makeOptions(200); +const options10 = makeOptions(10); + +function ComboboxBench({ options }: { options: Option[] }) { + return ( + item.label} + itemToStringValue={(item: Option) => item.value} + > + + + + + + {(item: Option) => ( + + {item.label} + + )} + + + + + ); +} + +describe('Combobox', () => { + benchRender( + 'mount with 100 options', + () => , + TIER_1_OPTIONS, + ); + + benchRerender( + 'options change (100 → 200)', + { initialProps: { options: options100 }, updatedProps: { options: options200 } }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 100 instances (10 options each)', + 100, + (i) => , + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/ContextMenu.bench.tsx b/packages/benchmarks/src/base-ui/ContextMenu.bench.tsx new file mode 100644 index 0000000..3b1e528 --- /dev/null +++ b/packages/benchmarks/src/base-ui/ContextMenu.bench.tsx @@ -0,0 +1,75 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { ContextMenu } from '@omniview/base-ui'; + +describe('ContextMenu', () => { + benchRender( + 'mount with items', + () => ( + + +
Right-click me
+
+ + + + Cut + Copy + Paste + + Delete + + + +
+ ), + TIER_1_OPTIONS, + ); + + benchRerender( + 'disabled toggle on item', + { + initialProps: { itemDisabled: false }, + updatedProps: { itemDisabled: true }, + }, + (props) => ( + + +
Right-click me
+
+ + + + Cut + Copy + Paste + + + +
+ ), + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 50 context menus', + 50, + (i) => ( + + +
Trigger {i}
+
+ + + + Action A + Action B + + + +
+ ), + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Dialog.bench.tsx b/packages/benchmarks/src/base-ui/Dialog.bench.tsx new file mode 100644 index 0000000..854df68 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Dialog.bench.tsx @@ -0,0 +1,53 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { Dialog } from '@omniview/base-ui'; + +const noop = () => {}; + +describe('Dialog', () => { + benchRender( + 'mount (open)', + () => ( + + Title + Body content + + + + + + ), + TIER_1_OPTIONS, + ); + + benchRerender( + 'open/close toggle', + { + initialProps: { open: true as boolean }, + updatedProps: { open: false as boolean }, + }, + (props) => ( + + Title + Body content + + + + + ), + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 50 dialogs', + 50, + (i) => ( + + Dialog {i} + Content {i} + + ), + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/DockLayout.bench.tsx b/packages/benchmarks/src/base-ui/DockLayout.bench.tsx new file mode 100644 index 0000000..2d97cd3 --- /dev/null +++ b/packages/benchmarks/src/base-ui/DockLayout.bench.tsx @@ -0,0 +1,101 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { DockLayout, type DockNode } from '@omniview/base-ui'; + +const twoPanel: DockNode = { + type: 'split', + direction: 'horizontal', + children: [ + { + type: 'leaf', + id: 'left', + tabs: [ + { id: 'tab-1', title: 'File A', content:
Content A
}, + { id: 'tab-2', title: 'File B', content:
Content B
}, + ], + activeTab: 'tab-1', + }, + { + type: 'leaf', + id: 'right', + tabs: [ + { id: 'tab-3', title: 'File C', content:
Content C
}, + ], + activeTab: 'tab-3', + }, + ], + sizes: [1, 1], +}; + +const threePanelVertical: DockNode = { + type: 'split', + direction: 'vertical', + children: [ + { + type: 'leaf', + id: 'top', + tabs: [{ id: 'tab-a', title: 'Top', content:
Top
}], + activeTab: 'tab-a', + }, + { + type: 'leaf', + id: 'middle', + tabs: [{ id: 'tab-b', title: 'Middle', content:
Middle
}], + activeTab: 'tab-b', + }, + { + type: 'leaf', + id: 'bottom', + tabs: [{ id: 'tab-c', title: 'Bottom', content:
Bottom
}], + activeTab: 'tab-c', + }, + ], + sizes: [1, 1, 1], +}; + +describe('DockLayout', () => { + benchRender( + 'mount 2-panel layout', + () => , + TIER_1_OPTIONS, + ); + + benchRerender( + 'layout change (2-panel → 3-panel vertical)', + { + initialProps: { layout: twoPanel }, + updatedProps: { layout: threePanelVertical }, + }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 20 dock layouts', + 20, + (i) => { + const layout: DockNode = { + type: 'split', + direction: 'horizontal', + children: [ + { + type: 'leaf', + id: `left-${i}`, + tabs: [{ id: `t-${i}-a`, title: `Tab A ${i}`, content:
A{i}
}], + activeTab: `t-${i}-a`, + }, + { + type: 'leaf', + id: `right-${i}`, + tabs: [{ id: `t-${i}-b`, title: `Tab B ${i}`, content:
B{i}
}], + activeTab: `t-${i}-b`, + }, + ], + sizes: [1, 1], + }; + return ; + }, + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Drawer.bench.tsx b/packages/benchmarks/src/base-ui/Drawer.bench.tsx new file mode 100644 index 0000000..423e438 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Drawer.bench.tsx @@ -0,0 +1,43 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { Drawer } from '@omniview/base-ui'; + +const noop = () => {}; + +describe('Drawer', () => { + benchRender( + 'mount (open)', + () => ( + + Drawer content + + ), + TIER_1_OPTIONS, + ); + + benchRerender( + 'open/close toggle', + { + initialProps: { open: true as boolean }, + updatedProps: { open: false as boolean }, + }, + (props) => ( + + Drawer content + + ), + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 50 drawers', + 50, + (i) => ( + + Content {i} + + ), + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx b/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx new file mode 100644 index 0000000..cf11fb5 --- /dev/null +++ b/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx @@ -0,0 +1,43 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { EditorTabs, type TabDescriptor } from '@omniview/base-ui'; + +function makeTabs(count: number, prefix = ''): TabDescriptor[] { + return Array.from({ length: count }, (_, i) => ({ + id: `${prefix}tab-${i}`, + title: `File ${prefix}${i}.ts`, + closable: true, + })); +} + +const tabs20 = makeTabs(20); +const tabs40 = makeTabs(40); + +describe('EditorTabs', () => { + benchRender( + 'mount 20 tabs', + () => , + TIER_1_OPTIONS, + ); + + benchRerender( + 'tabs change (20 → 40)', + { + initialProps: { tabs: tabs20 }, + updatedProps: { tabs: tabs40 }, + }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 30 tab bars (5 tabs each)', + 30, + (i) => { + const tabs = makeTabs(5, `bar${i}-`); + return ; + }, + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/FilterBar.bench.tsx b/packages/benchmarks/src/base-ui/FilterBar.bench.tsx new file mode 100644 index 0000000..5474e45 --- /dev/null +++ b/packages/benchmarks/src/base-ui/FilterBar.bench.tsx @@ -0,0 +1,44 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeTags } from '../utils/factories'; +import { FilterBar } from '@omniview/base-ui'; + +const filters10 = makeTags(10); +const filters20 = makeTags(20); +const filters5 = makeTags(5); + +function FilterBarBench({ filters }: { filters: string[] }) { + return ( + + {filters.map((filter) => ( + {}}> + {filter} + + ))} + + + ); +} + +describe('FilterBar', () => { + benchRender( + 'mount with 10 filters', + () => , + TIER_1_OPTIONS, + ); + + benchRerender( + 'filters change (10 → 20)', + { initialProps: { filters: filters10 }, updatedProps: { filters: filters20 } }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 100 instances (5 filters each)', + 100, + (i) => , + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx b/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx new file mode 100644 index 0000000..5dc481f --- /dev/null +++ b/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx @@ -0,0 +1,47 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeOptions, type Option } from '../utils/factories'; +import { MultiSelect } from '@omniview/base-ui'; + +const options100 = makeOptions(100); +const options10 = makeOptions(10); +const noSelection: Option[] = []; +const selected20 = options100.slice(0, 20); + +function MultiSelectBench({ options, value }: { options: Option[]; value?: Option[] }) { + return ( + + items={options} + value={value} + getItemLabel={(item) => item.label} + getItemValue={(item) => item.value} + placeholder="Select options" + /> + ); +} + +describe('MultiSelect', () => { + benchRender( + 'mount with 100 options', + () => , + TIER_1_OPTIONS, + ); + + benchRerender( + 'selection change (0 → 20 selected)', + { + initialProps: { options: options100, value: noSelection }, + updatedProps: { options: options100, value: selected20 }, + }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 100 instances (10 options each)', + 100, + (i) => , + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Popover.bench.tsx b/packages/benchmarks/src/base-ui/Popover.bench.tsx new file mode 100644 index 0000000..15629fe --- /dev/null +++ b/packages/benchmarks/src/base-ui/Popover.bench.tsx @@ -0,0 +1,65 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { Popover } from '@omniview/base-ui'; + +describe('Popover', () => { + benchRender( + 'mount (open)', + () => ( + + Open + + + + Info + Popover content here. + Close + + + + + ), + TIER_1_OPTIONS, + ); + + benchRerender( + 'open/close toggle', + { + initialProps: { open: true }, + updatedProps: { open: false }, + }, + (props) => ( + + Open + + + + Info + Content + + + + + ), + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 50 popovers', + 50, + (i) => ( + + Trigger {i} + + + + Content {i} + + + + + ), + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/ResizableSplitPane.bench.tsx b/packages/benchmarks/src/base-ui/ResizableSplitPane.bench.tsx new file mode 100644 index 0000000..26b35a7 --- /dev/null +++ b/packages/benchmarks/src/base-ui/ResizableSplitPane.bench.tsx @@ -0,0 +1,41 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { ResizableSplitPane, type SplitDirection } from '@omniview/base-ui'; + +describe('ResizableSplitPane', () => { + benchRender( + 'mount horizontal', + () => ( + + {[
Left
,
Right
]} +
+ ), + TIER_1_OPTIONS, + ); + + benchRerender( + 'direction change (horizontal → vertical)', + { + initialProps: { direction: 'horizontal' as SplitDirection }, + updatedProps: { direction: 'vertical' as SplitDirection }, + }, + (props) => ( + + {[
Pane 1
,
Pane 2
]} +
+ ), + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 30 split panes', + 30, + (i) => ( + + {[
Left {i}
,
Right {i}
]} +
+ ), + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Sheet.bench.tsx b/packages/benchmarks/src/base-ui/Sheet.bench.tsx new file mode 100644 index 0000000..bd0cd18 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Sheet.bench.tsx @@ -0,0 +1,42 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { Sheet } from '@omniview/base-ui'; +import type { SurfaceElevation } from '@omniview/base-ui'; + +describe('Sheet', () => { + benchRender( + 'mount', + () => ( + +

Sheet content

+
+ ), + TIER_1_OPTIONS, + ); + + benchRerender( + 'elevation change', + { + initialProps: { elevation: 0 as SurfaceElevation }, + updatedProps: { elevation: 3 as SurfaceElevation }, + }, + (props) => ( + +

Sheet content

+
+ ), + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 50 sheets', + 50, + (i) => ( + +

Content {i}

+
+ ), + TIER_1_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/TagInput.bench.tsx b/packages/benchmarks/src/base-ui/TagInput.bench.tsx new file mode 100644 index 0000000..0d32e3e --- /dev/null +++ b/packages/benchmarks/src/base-ui/TagInput.bench.tsx @@ -0,0 +1,37 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { TIER_1_OPTIONS } from '../utils/bench-options'; +import { makeTags } from '../utils/factories'; +import { TagInput } from '@omniview/base-ui'; + +const tags10 = makeTags(10); +const tags20 = makeTags(20); +const tags5 = makeTags(5); + +const noop = () => {}; + +function TagInputBench({ tags }: { tags: string[] }) { + return ; +} + +describe('TagInput', () => { + benchRender( + 'mount with 10 tags', + () => , + TIER_1_OPTIONS, + ); + + benchRerender( + 'tags change (10 → 20)', + { initialProps: { tags: tags10 }, updatedProps: { tags: tags20 } }, + (props) => , + TIER_1_OPTIONS, + ); + + benchMountMany( + 'mount 100 instances (5 tags each)', + 100, + (i) => , + TIER_1_OPTIONS, + ); +}); From 6e4e62a732ca1668630bdfb4bf12a099be7594e8 Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 02:33:00 -0500 Subject: [PATCH 08/13] feat(benchmarks): add Tier 2 form input benchmarks --- .../src/base-ui/Accordion.bench.tsx | 34 +++++++++ .../src/base-ui/ActionList.bench.tsx | 38 ++++++++++ .../src/base-ui/AlertDialog.bench.tsx | 40 +++++++++++ .../benchmarks/src/base-ui/AppShell.bench.tsx | 34 +++++++++ .../benchmarks/src/base-ui/Banner.bench.tsx | 32 +++++++++ .../src/base-ui/Breadcrumbs.bench.tsx | 39 ++++++++++ .../src/base-ui/ButtonGroup.bench.tsx | 34 +++++++++ .../benchmarks/src/base-ui/Card.bench.tsx | 36 ++++++++++ .../src/base-ui/CheckboxGroup.bench.tsx | 27 +++++++ .../benchmarks/src/base-ui/Chip.bench.tsx | 22 ++++++ .../src/base-ui/ClipboardText.bench.tsx | 22 ++++++ .../src/base-ui/CodeBlock.bench.tsx | 25 +++++++ .../src/base-ui/Collapsible.bench.tsx | 32 +++++++++ .../src/base-ui/ConfirmButton.bench.tsx | 24 +++++++ .../src/base-ui/DescriptionList.bench.tsx | 40 +++++++++++ .../src/base-ui/EditableList.bench.tsx | 66 +++++++++++++++++ .../src/base-ui/EmptyState.bench.tsx | 22 ++++++ .../benchmarks/src/base-ui/FindBar.bench.tsx | 22 ++++++ .../src/base-ui/FormField.bench.tsx | 26 +++++++ .../src/base-ui/IconButton.bench.tsx | 22 ++++++ .../benchmarks/src/base-ui/Input.bench.tsx | 25 +++++++ .../benchmarks/src/base-ui/List.bench.tsx | 40 +++++++++++ .../benchmarks/src/base-ui/Menu.bench.tsx | 48 +++++++++++++ .../benchmarks/src/base-ui/Meter.bench.tsx | 22 ++++++ .../benchmarks/src/base-ui/NavList.bench.tsx | 46 ++++++++++++ .../src/base-ui/NumberInput.bench.tsx | 33 +++++++++ .../src/base-ui/Pagination.bench.tsx | 24 +++++++ .../benchmarks/src/base-ui/Progress.bench.tsx | 22 ++++++ .../benchmarks/src/base-ui/Radio.bench.tsx | 19 +++++ .../src/base-ui/RadioGroup.bench.tsx | 27 +++++++ .../src/base-ui/ScrollArea.bench.tsx | 30 ++++++++ .../src/base-ui/SearchInput.bench.tsx | 19 +++++ .../src/base-ui/SegmentedControl.bench.tsx | 34 +++++++++ .../benchmarks/src/base-ui/Select.bench.tsx | 56 +++++++++++++++ .../benchmarks/src/base-ui/Slider.bench.tsx | 33 +++++++++ .../benchmarks/src/base-ui/Spinner.bench.tsx | 22 ++++++ .../src/base-ui/SplitButton.bench.tsx | 36 ++++++++++ .../benchmarks/src/base-ui/StatRow.bench.tsx | 39 ++++++++++ .../src/base-ui/StatusBar.bench.tsx | 39 ++++++++++ .../benchmarks/src/base-ui/Stepper.bench.tsx | 34 +++++++++ .../benchmarks/src/base-ui/Switch.bench.tsx | 19 +++++ .../benchmarks/src/base-ui/Table.bench.tsx | 71 +++++++++++++++++++ .../benchmarks/src/base-ui/Tabs.bench.tsx | 44 ++++++++++++ .../benchmarks/src/base-ui/TextArea.bench.tsx | 25 +++++++ .../src/base-ui/TextField.bench.tsx | 25 +++++++ .../benchmarks/src/base-ui/Timeline.bench.tsx | 40 +++++++++++ .../benchmarks/src/base-ui/Toast.bench.tsx | 30 ++++++++ .../src/base-ui/ToggleButton.bench.tsx | 26 +++++++ .../src/base-ui/ToggleButtonGroup.bench.tsx | 34 +++++++++ .../benchmarks/src/base-ui/Toolbar.bench.tsx | 38 ++++++++++ .../benchmarks/src/base-ui/Tooltip.bench.tsx | 40 +++++++++++ 51 files changed, 1677 insertions(+) create mode 100644 packages/benchmarks/src/base-ui/Accordion.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/ActionList.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/AlertDialog.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/AppShell.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Banner.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/ButtonGroup.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Card.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/CheckboxGroup.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Chip.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/ClipboardText.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/CodeBlock.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Collapsible.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/DescriptionList.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/EditableList.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/EmptyState.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/FindBar.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/FormField.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/IconButton.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Input.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/List.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Menu.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Meter.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/NavList.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/NumberInput.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Pagination.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Progress.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Radio.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/RadioGroup.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/ScrollArea.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/SearchInput.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/SegmentedControl.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Select.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Slider.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Spinner.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/SplitButton.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/StatRow.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/StatusBar.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Stepper.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Switch.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Table.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Tabs.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/TextArea.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/TextField.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Timeline.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Toast.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/ToggleButton.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/ToggleButtonGroup.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Toolbar.bench.tsx create mode 100644 packages/benchmarks/src/base-ui/Tooltip.bench.tsx diff --git a/packages/benchmarks/src/base-ui/Accordion.bench.tsx b/packages/benchmarks/src/base-ui/Accordion.bench.tsx new file mode 100644 index 0000000..4ed6296 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Accordion.bench.tsx @@ -0,0 +1,34 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Accordion } from '@omniview/base-ui'; + +describe('Accordion', () => { + benchRender( + 'mount with 3 items', + () => ( + + Content A + Content B + Content C + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'expand toggle', + { + initialProps: { expanded: [] as string[] }, + updatedProps: { expanded: ['a'] as string[] }, + }, + (props) => ( + + Content A + Content B + Content C + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/ActionList.bench.tsx b/packages/benchmarks/src/base-ui/ActionList.bench.tsx new file mode 100644 index 0000000..5ade470 --- /dev/null +++ b/packages/benchmarks/src/base-ui/ActionList.bench.tsx @@ -0,0 +1,38 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { ActionList } from '@omniview/base-ui'; + +describe('ActionList', () => { + benchRender( + 'mount with items', + () => ( + + + + + + + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'disabled toggle', + { + initialProps: { disabled: false as boolean }, + updatedProps: { disabled: true as boolean }, + }, + (props) => ( + + + + + + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx b/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx new file mode 100644 index 0000000..02f2df1 --- /dev/null +++ b/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx @@ -0,0 +1,40 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { AlertDialog } from '@omniview/base-ui'; + +describe('AlertDialog', () => { + benchRender( + 'mount (open)', + () => ( + + + + Confirm + Are you sure? + Cancel + + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'open toggle', + { + initialProps: { open: true as boolean }, + updatedProps: { open: false as boolean }, + }, + (props) => ( + + + + Confirm + Are you sure? + Cancel + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/AppShell.bench.tsx b/packages/benchmarks/src/base-ui/AppShell.bench.tsx new file mode 100644 index 0000000..1023e2b --- /dev/null +++ b/packages/benchmarks/src/base-ui/AppShell.bench.tsx @@ -0,0 +1,34 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { AppShell } from '@omniview/base-ui'; + +describe('AppShell', () => { + benchRender( + 'mount with Header/Sidebar/Content', + () => ( + + Header + Sidebar + Content + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'sidebar visibility toggle', + { + initialProps: { sidebarCollapsed: false as boolean }, + updatedProps: { sidebarCollapsed: true as boolean }, + }, + (props) => ( + + Header + Sidebar + Content + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Banner.bench.tsx b/packages/benchmarks/src/base-ui/Banner.bench.tsx new file mode 100644 index 0000000..3020014 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Banner.bench.tsx @@ -0,0 +1,32 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Banner } from '@omniview/base-ui'; + +describe('Banner', () => { + benchRender( + 'mount', + () => ( + + Notice + This is a banner message. + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'variant change', + { + initialProps: { variant: 'soft' as const }, + updatedProps: { variant: 'outline' as const }, + }, + (props) => ( + + Notice + This is a banner message. + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx b/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx new file mode 100644 index 0000000..155ceee --- /dev/null +++ b/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx @@ -0,0 +1,39 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Breadcrumbs } from '@omniview/base-ui'; + +const threeItems = ( + <> + Home + Docs + Current + +); + +const fourItems = ( + <> + Home + Docs + API + Current + +); + +describe('Breadcrumbs', () => { + benchRender( + 'mount with 3 items', + () => {threeItems}, + TIER_2_OPTIONS, + ); + + benchRerender( + 'items change', + { + initialProps: { children: threeItems }, + updatedProps: { children: fourItems }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/ButtonGroup.bench.tsx b/packages/benchmarks/src/base-ui/ButtonGroup.bench.tsx new file mode 100644 index 0000000..abba5ca --- /dev/null +++ b/packages/benchmarks/src/base-ui/ButtonGroup.bench.tsx @@ -0,0 +1,34 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { ButtonGroup } from '@omniview/base-ui'; + +describe('ButtonGroup', () => { + benchRender( + 'mount with 3 buttons', + () => ( + + One + Two + Three + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'variant change', + { + initialProps: { variant: 'soft' as const }, + updatedProps: { variant: 'outline' as const }, + }, + (props) => ( + + One + Two + Three + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Card.bench.tsx b/packages/benchmarks/src/base-ui/Card.bench.tsx new file mode 100644 index 0000000..3101e83 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Card.bench.tsx @@ -0,0 +1,36 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Card } from '@omniview/base-ui'; + +describe('Card', () => { + benchRender( + 'mount with Header/Title/Body', + () => ( + + + Title + + Body content + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'variant change', + { + initialProps: { variant: 'soft' as const }, + updatedProps: { variant: 'outline' as const }, + }, + (props) => ( + + + Title + + Body content + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/CheckboxGroup.bench.tsx b/packages/benchmarks/src/base-ui/CheckboxGroup.bench.tsx new file mode 100644 index 0000000..1f12b05 --- /dev/null +++ b/packages/benchmarks/src/base-ui/CheckboxGroup.bench.tsx @@ -0,0 +1,27 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { CheckboxGroup } from '@omniview/base-ui'; + +describe('CheckboxGroup', () => { + benchRender('mount with 3 items', () => ( + + Option A + Option B + Option C + + ), TIER_2_OPTIONS); + + benchRerender( + 'value change', + { initialProps: { value: ['a'] as string[] }, updatedProps: { value: ['a', 'b'] as string[] } }, + (props) => ( + + Option A + Option B + Option C + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Chip.bench.tsx b/packages/benchmarks/src/base-ui/Chip.bench.tsx new file mode 100644 index 0000000..807c443 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Chip.bench.tsx @@ -0,0 +1,22 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Chip } from '@omniview/base-ui'; + +describe('Chip', () => { + benchRender( + 'mount', + () => Label, + TIER_2_OPTIONS, + ); + + benchRerender( + 'variant change', + { + initialProps: { variant: 'soft' as const }, + updatedProps: { variant: 'outline' as const }, + }, + (props) => Label, + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx b/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx new file mode 100644 index 0000000..66f3356 --- /dev/null +++ b/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx @@ -0,0 +1,22 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { ClipboardText } from '@omniview/base-ui'; + +describe('ClipboardText', () => { + benchRender( + 'mount', + () => , + TIER_2_OPTIONS, + ); + + benchRerender( + 'value change', + { + initialProps: { value: 'kubectl get pods' }, + updatedProps: { value: 'kubectl get nodes' }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/CodeBlock.bench.tsx b/packages/benchmarks/src/base-ui/CodeBlock.bench.tsx new file mode 100644 index 0000000..9d3fce3 --- /dev/null +++ b/packages/benchmarks/src/base-ui/CodeBlock.bench.tsx @@ -0,0 +1,25 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { CodeBlock } from '@omniview/base-ui'; + +const codeA = 'const x = 1;\nconsole.log(x);'; +const codeB = 'const y = 2;\nconsole.log(y);'; + +describe('CodeBlock', () => { + benchRender( + 'mount', + () => {codeA}, + TIER_2_OPTIONS, + ); + + benchRerender( + 'code change', + { + initialProps: { children: codeA }, + updatedProps: { children: codeB }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Collapsible.bench.tsx b/packages/benchmarks/src/base-ui/Collapsible.bench.tsx new file mode 100644 index 0000000..f6411a6 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Collapsible.bench.tsx @@ -0,0 +1,32 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@omniview/base-ui'; + +describe('Collapsible', () => { + benchRender( + 'mount', + () => ( + + Toggle + Collapsible content here + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'open toggle', + { + initialProps: { open: false as boolean }, + updatedProps: { open: true as boolean }, + }, + (props) => ( + + Toggle + Collapsible content here + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx b/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx new file mode 100644 index 0000000..7953179 --- /dev/null +++ b/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx @@ -0,0 +1,24 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { ConfirmButton } from '@omniview/base-ui'; + +const noop = () => {}; + +describe('ConfirmButton', () => { + benchRender( + 'mount', + () => Delete, + TIER_2_OPTIONS, + ); + + benchRerender( + 'disabled toggle', + { + initialProps: { disabled: false as boolean, onConfirm: noop, children: 'Delete' as const }, + updatedProps: { disabled: true as boolean, onConfirm: noop, children: 'Delete' as const }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx b/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx new file mode 100644 index 0000000..0bfbd0c --- /dev/null +++ b/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx @@ -0,0 +1,40 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { DescriptionList } from '@omniview/base-ui'; +import type { ReactNode } from 'react'; + +const threeItems = ( + <> + Widget + Active + 1.0.0 + +); + +const fourItems = ( + <> + Widget + Active + 1.0.0 + US-East + +); + +describe('DescriptionList', () => { + benchRender( + 'mount with 3 items', + () => {threeItems}, + TIER_2_OPTIONS, + ); + + benchRerender( + 'items change', + { + initialProps: { children: threeItems as ReactNode }, + updatedProps: { children: fourItems as ReactNode }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/EditableList.bench.tsx b/packages/benchmarks/src/base-ui/EditableList.bench.tsx new file mode 100644 index 0000000..98f169f --- /dev/null +++ b/packages/benchmarks/src/base-ui/EditableList.bench.tsx @@ -0,0 +1,66 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { EditableList } from '@omniview/base-ui'; + +const noop = () => {}; + +describe('EditableList', () => { + benchRender( + 'mount with items', + () => ( + + + Item A + + + + + + Item B + + + + + + Item C + + + + + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'editable toggle', + { + initialProps: { editable: true as boolean }, + updatedProps: { editable: false as boolean }, + }, + (props) => ( + + + Item A + + + + + + Item B + + + + + + Item C + + + + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/EmptyState.bench.tsx b/packages/benchmarks/src/base-ui/EmptyState.bench.tsx new file mode 100644 index 0000000..8814de8 --- /dev/null +++ b/packages/benchmarks/src/base-ui/EmptyState.bench.tsx @@ -0,0 +1,22 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { EmptyState } from '@omniview/base-ui'; + +describe('EmptyState', () => { + benchRender( + 'mount', + () => , + TIER_2_OPTIONS, + ); + + benchRerender( + 'title change', + { + initialProps: { title: 'No results' }, + updatedProps: { title: 'Nothing found' }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/FindBar.bench.tsx b/packages/benchmarks/src/base-ui/FindBar.bench.tsx new file mode 100644 index 0000000..c14e45f --- /dev/null +++ b/packages/benchmarks/src/base-ui/FindBar.bench.tsx @@ -0,0 +1,22 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { FindBar } from '@omniview/base-ui'; + +describe('FindBar', () => { + benchRender( + 'mount', + () => , + TIER_2_OPTIONS, + ); + + benchRerender( + 'value change', + { + initialProps: { open: true as const, query: '' }, + updatedProps: { open: true as const, query: 'search term' }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/FormField.bench.tsx b/packages/benchmarks/src/base-ui/FormField.bench.tsx new file mode 100644 index 0000000..6bf2ce1 --- /dev/null +++ b/packages/benchmarks/src/base-ui/FormField.bench.tsx @@ -0,0 +1,26 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { FormField } from '@omniview/base-ui'; + +describe('FormField', () => { + benchRender('mount', () => ( + + + + ), TIER_2_OPTIONS); + + benchRerender( + 'error toggle', + { + initialProps: { error: undefined as string | undefined }, + updatedProps: { error: 'This field is required' }, + }, + (props) => ( + + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/IconButton.bench.tsx b/packages/benchmarks/src/base-ui/IconButton.bench.tsx new file mode 100644 index 0000000..0eb4761 --- /dev/null +++ b/packages/benchmarks/src/base-ui/IconButton.bench.tsx @@ -0,0 +1,22 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { IconButton } from '@omniview/base-ui'; + +describe('IconButton', () => { + benchRender( + 'mount', + () => E, + TIER_2_OPTIONS, + ); + + benchRerender( + 'variant change', + { + initialProps: { variant: 'soft' as const, 'aria-label': 'Edit' }, + updatedProps: { variant: 'outline' as const, 'aria-label': 'Edit' }, + }, + (props) => E, + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Input.bench.tsx b/packages/benchmarks/src/base-ui/Input.bench.tsx new file mode 100644 index 0000000..b2658a9 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Input.bench.tsx @@ -0,0 +1,25 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Input } from '@omniview/base-ui'; + +describe('Input', () => { + benchRender('mount', () => ( + + Label + + + ), TIER_2_OPTIONS); + + benchRerender( + 'disabled toggle', + { initialProps: { disabled: false }, updatedProps: { disabled: true } }, + (props) => ( + + Label + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/List.bench.tsx b/packages/benchmarks/src/base-ui/List.bench.tsx new file mode 100644 index 0000000..b5d2122 --- /dev/null +++ b/packages/benchmarks/src/base-ui/List.bench.tsx @@ -0,0 +1,40 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { List } from '@omniview/base-ui'; +import type { ReactNode } from 'react'; + +const threeItems = ( + <> + Alpha + Beta + Gamma + +); + +const fourItems = ( + <> + Alpha + Beta + Gamma + Delta + +); + +describe('List', () => { + benchRender( + 'mount with 3 items', + () => {threeItems}, + TIER_2_OPTIONS, + ); + + benchRerender( + 'items change', + { + initialProps: { children: threeItems as ReactNode }, + updatedProps: { children: fourItems as ReactNode }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Menu.bench.tsx b/packages/benchmarks/src/base-ui/Menu.bench.tsx new file mode 100644 index 0000000..a7d4ece --- /dev/null +++ b/packages/benchmarks/src/base-ui/Menu.bench.tsx @@ -0,0 +1,48 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Menu } from '@omniview/base-ui'; + +describe('Menu', () => { + benchRender( + 'mount', + () => ( + + Open + + + + Item 1 + Item 2 + Item 3 + + + + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'open toggle', + { + initialProps: { open: false as boolean }, + updatedProps: { open: true as boolean }, + }, + (props) => ( + + Open + + + + Item 1 + Item 2 + Item 3 + + + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Meter.bench.tsx b/packages/benchmarks/src/base-ui/Meter.bench.tsx new file mode 100644 index 0000000..7431f71 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Meter.bench.tsx @@ -0,0 +1,22 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Meter } from '@omniview/base-ui'; + +describe('Meter', () => { + benchRender( + 'mount', + () => , + TIER_2_OPTIONS, + ); + + benchRerender( + 'value change', + { + initialProps: { value: 60, label: 'CPU Usage' as const }, + updatedProps: { value: 85, label: 'CPU Usage' as const }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/NavList.bench.tsx b/packages/benchmarks/src/base-ui/NavList.bench.tsx new file mode 100644 index 0000000..f3908be --- /dev/null +++ b/packages/benchmarks/src/base-ui/NavList.bench.tsx @@ -0,0 +1,46 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { NavList } from '@omniview/base-ui'; + +describe('NavList', () => { + benchRender( + 'mount with items', + () => ( + + + Home + + + Settings + + + Profile + + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'active item change', + { + initialProps: { activeKey: 'home' as string }, + updatedProps: { activeKey: 'settings' as string }, + }, + (props) => ( + + + Home + + + Settings + + + Profile + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/NumberInput.bench.tsx b/packages/benchmarks/src/base-ui/NumberInput.bench.tsx new file mode 100644 index 0000000..e7d0ac4 --- /dev/null +++ b/packages/benchmarks/src/base-ui/NumberInput.bench.tsx @@ -0,0 +1,33 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { NumberInput } from '@omniview/base-ui'; + +describe('NumberInput', () => { + benchRender('mount', () => ( + + Quantity + + + + + + + ), TIER_2_OPTIONS); + + benchRerender( + 'disabled toggle', + { initialProps: { disabled: false }, updatedProps: { disabled: true } }, + (props) => ( + + Quantity + + + + + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Pagination.bench.tsx b/packages/benchmarks/src/base-ui/Pagination.bench.tsx new file mode 100644 index 0000000..49bf209 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Pagination.bench.tsx @@ -0,0 +1,24 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Pagination } from '@omniview/base-ui'; + +const noop = () => {}; + +describe('Pagination', () => { + benchRender( + 'mount', + () => , + TIER_2_OPTIONS, + ); + + benchRerender( + 'page change', + { + initialProps: { page: 1 as number }, + updatedProps: { page: 5 as number }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Progress.bench.tsx b/packages/benchmarks/src/base-ui/Progress.bench.tsx new file mode 100644 index 0000000..b274f5a --- /dev/null +++ b/packages/benchmarks/src/base-ui/Progress.bench.tsx @@ -0,0 +1,22 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Progress } from '@omniview/base-ui'; + +describe('Progress', () => { + benchRender( + 'mount', + () => , + TIER_2_OPTIONS, + ); + + benchRerender( + 'value change', + { + initialProps: { value: 50 }, + updatedProps: { value: 80 }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Radio.bench.tsx b/packages/benchmarks/src/base-ui/Radio.bench.tsx new file mode 100644 index 0000000..2c989c4 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Radio.bench.tsx @@ -0,0 +1,19 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Radio } from '@omniview/base-ui'; + +describe('Radio', () => { + benchRender('mount', () => ( + Option 1 + ), TIER_2_OPTIONS); + + benchRerender( + 'disabled toggle', + { initialProps: { disabled: false }, updatedProps: { disabled: true } }, + (props) => ( + Option 1 + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/RadioGroup.bench.tsx b/packages/benchmarks/src/base-ui/RadioGroup.bench.tsx new file mode 100644 index 0000000..3775199 --- /dev/null +++ b/packages/benchmarks/src/base-ui/RadioGroup.bench.tsx @@ -0,0 +1,27 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { RadioGroup } from '@omniview/base-ui'; + +describe('RadioGroup', () => { + benchRender('mount with 3 items', () => ( + + Option A + Option B + Option C + + ), TIER_2_OPTIONS); + + benchRerender( + 'value change', + { initialProps: { value: 'a' }, updatedProps: { value: 'b' } }, + (props) => ( + + Option A + Option B + Option C + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/ScrollArea.bench.tsx b/packages/benchmarks/src/base-ui/ScrollArea.bench.tsx new file mode 100644 index 0000000..480f429 --- /dev/null +++ b/packages/benchmarks/src/base-ui/ScrollArea.bench.tsx @@ -0,0 +1,30 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { ScrollArea } from '@omniview/base-ui'; +import type { ReactNode } from 'react'; + +const contentA =
Scrollable content block A
; +const contentB =
Scrollable content block B with extra text
; + +describe('ScrollArea', () => { + benchRender( + 'mount', + () => ( + + {contentA} + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'content change', + { + initialProps: { children: contentA as ReactNode }, + updatedProps: { children: contentB as ReactNode }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/SearchInput.bench.tsx b/packages/benchmarks/src/base-ui/SearchInput.bench.tsx new file mode 100644 index 0000000..85dc177 --- /dev/null +++ b/packages/benchmarks/src/base-ui/SearchInput.bench.tsx @@ -0,0 +1,19 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { SearchInput } from '@omniview/base-ui'; + +describe('SearchInput', () => { + benchRender('mount', () => ( + {}} /> + ), TIER_2_OPTIONS); + + benchRerender( + 'value change', + { initialProps: { value: '' }, updatedProps: { value: 'search query' } }, + (props) => ( + {}} {...props} /> + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/SegmentedControl.bench.tsx b/packages/benchmarks/src/base-ui/SegmentedControl.bench.tsx new file mode 100644 index 0000000..4a648f3 --- /dev/null +++ b/packages/benchmarks/src/base-ui/SegmentedControl.bench.tsx @@ -0,0 +1,34 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { SegmentedControl } from '@omniview/base-ui'; + +describe('SegmentedControl', () => { + benchRender( + 'mount with 3 items', + () => ( + + Option A + Option B + Option C + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'value change', + { + initialProps: { value: 'a' as string }, + updatedProps: { value: 'c' as string }, + }, + (props) => ( + + Option A + Option B + Option C + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Select.bench.tsx b/packages/benchmarks/src/base-ui/Select.bench.tsx new file mode 100644 index 0000000..93e650a --- /dev/null +++ b/packages/benchmarks/src/base-ui/Select.bench.tsx @@ -0,0 +1,56 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { makeOptions } from '../utils/factories'; +import { Select } from '@omniview/base-ui'; + +const options = makeOptions(20); + +describe('Select', () => { + benchRender('mount with 20 options', () => ( + + ), TIER_2_OPTIONS); + + benchRerender( + 'disabled toggle', + { initialProps: { disabled: false }, updatedProps: { disabled: true } }, + (props) => ( + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Slider.bench.tsx b/packages/benchmarks/src/base-ui/Slider.bench.tsx new file mode 100644 index 0000000..81c62f5 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Slider.bench.tsx @@ -0,0 +1,33 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Slider } from '@omniview/base-ui'; + +describe('Slider', () => { + benchRender('mount', () => ( + + + + + + + + + ), TIER_2_OPTIONS); + + benchRerender( + 'value change', + { initialProps: { value: 25 }, updatedProps: { value: 75 } }, + (props) => ( + + + + + + + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Spinner.bench.tsx b/packages/benchmarks/src/base-ui/Spinner.bench.tsx new file mode 100644 index 0000000..db2c496 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Spinner.bench.tsx @@ -0,0 +1,22 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Spinner } from '@omniview/base-ui'; + +describe('Spinner', () => { + benchRender( + 'mount', + () => , + TIER_2_OPTIONS, + ); + + benchRerender( + 'size change', + { + initialProps: { size: 'md' as const }, + updatedProps: { size: 'lg' as const }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/SplitButton.bench.tsx b/packages/benchmarks/src/base-ui/SplitButton.bench.tsx new file mode 100644 index 0000000..3d5a999 --- /dev/null +++ b/packages/benchmarks/src/base-ui/SplitButton.bench.tsx @@ -0,0 +1,36 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { SplitButton } from '@omniview/base-ui'; + +describe('SplitButton', () => { + benchRender( + 'mount', + () => ( + + Save + +
Option 1
+
+
+ ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'disabled toggle', + { + initialProps: { disabled: false as boolean }, + updatedProps: { disabled: true as boolean }, + }, + (props) => ( + + Save + +
Option 1
+
+
+ ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/StatRow.bench.tsx b/packages/benchmarks/src/base-ui/StatRow.bench.tsx new file mode 100644 index 0000000..1d4c3c8 --- /dev/null +++ b/packages/benchmarks/src/base-ui/StatRow.bench.tsx @@ -0,0 +1,39 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { StatRow } from '@omniview/base-ui'; +import type { ReactNode } from 'react'; + +const itemsA = ( + <> + CPU: 45% + Mem: 2.1 GB + Pods: 12 + +); + +const itemsB = ( + <> + CPU: 72% + Mem: 3.4 GB + Pods: 18 + +); + +describe('StatRow', () => { + benchRender( + 'mount', + () => {itemsA}, + TIER_2_OPTIONS, + ); + + benchRerender( + 'value change', + { + initialProps: { children: itemsA as ReactNode }, + updatedProps: { children: itemsB as ReactNode }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/StatusBar.bench.tsx b/packages/benchmarks/src/base-ui/StatusBar.bench.tsx new file mode 100644 index 0000000..a2e3183 --- /dev/null +++ b/packages/benchmarks/src/base-ui/StatusBar.bench.tsx @@ -0,0 +1,39 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { StatusBar } from '@omniview/base-ui'; +import type { ReactNode } from 'react'; + +const contentA = ( + + main + + UTF-8 + +); + +const contentB = ( + + develop + + UTF-16 + +); + +describe('StatusBar', () => { + benchRender( + 'mount', + () => {contentA}, + TIER_2_OPTIONS, + ); + + benchRerender( + 'content change', + { + initialProps: { children: contentA as ReactNode }, + updatedProps: { children: contentB as ReactNode }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Stepper.bench.tsx b/packages/benchmarks/src/base-ui/Stepper.bench.tsx new file mode 100644 index 0000000..d00a1f1 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Stepper.bench.tsx @@ -0,0 +1,34 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Stepper } from '@omniview/base-ui'; + +describe('Stepper', () => { + benchRender( + 'mount with 3 steps', + () => ( + + + + + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'active step change', + { + initialProps: { activeStep: 0 as number }, + updatedProps: { activeStep: 2 as number }, + }, + (props) => ( + + + + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Switch.bench.tsx b/packages/benchmarks/src/base-ui/Switch.bench.tsx new file mode 100644 index 0000000..def7008 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Switch.bench.tsx @@ -0,0 +1,19 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Switch } from '@omniview/base-ui'; + +describe('Switch', () => { + benchRender('mount', () => ( + Enable notifications + ), TIER_2_OPTIONS); + + benchRerender( + 'checked toggle', + { initialProps: { checked: false }, updatedProps: { checked: true } }, + (props) => ( + Enable notifications + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Table.bench.tsx b/packages/benchmarks/src/base-ui/Table.bench.tsx new file mode 100644 index 0000000..6ad570a --- /dev/null +++ b/packages/benchmarks/src/base-ui/Table.bench.tsx @@ -0,0 +1,71 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Table } from '@omniview/base-ui'; +import type { ReactNode } from 'react'; + +const rowsA = ( + + + Alice + 42 + + + Bob + 37 + + + Charlie + 29 + + +); + +const rowsB = ( + + + Diana + 55 + + + Eve + 31 + + + Frank + 48 + + +); + +const header = ( + + + Name + Age + + +); + +describe('Table', () => { + benchRender( + 'mount with rows', + () => ( + + {header} + {rowsA} +
+ ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'rows change', + { + initialProps: { children: <>{header}{rowsA} as ReactNode }, + updatedProps: { children: <>{header}{rowsB} as ReactNode }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Tabs.bench.tsx b/packages/benchmarks/src/base-ui/Tabs.bench.tsx new file mode 100644 index 0000000..0505482 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Tabs.bench.tsx @@ -0,0 +1,44 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Tabs } from '@omniview/base-ui'; + +describe('Tabs', () => { + benchRender( + 'mount with 3 tabs', + () => ( + + + Tab 1 + Tab 2 + Tab 3 + + Panel 1 + Panel 2 + Panel 3 + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'active tab change', + { + initialProps: { value: 0 as number }, + updatedProps: { value: 2 as number }, + }, + (props) => ( + + + Tab 1 + Tab 2 + Tab 3 + + Panel 1 + Panel 2 + Panel 3 + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/TextArea.bench.tsx b/packages/benchmarks/src/base-ui/TextArea.bench.tsx new file mode 100644 index 0000000..2ae331d --- /dev/null +++ b/packages/benchmarks/src/base-ui/TextArea.bench.tsx @@ -0,0 +1,25 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { TextArea } from '@omniview/base-ui'; + +describe('TextArea', () => { + benchRender('mount', () => ( + + ), TIER_2_OPTIONS); + + benchRerender( + 'disabled toggle', + { initialProps: { disabled: false }, updatedProps: { disabled: true } }, + (props) => ( + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/TextField.bench.tsx b/packages/benchmarks/src/base-ui/TextField.bench.tsx new file mode 100644 index 0000000..616a614 --- /dev/null +++ b/packages/benchmarks/src/base-ui/TextField.bench.tsx @@ -0,0 +1,25 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { TextField } from '@omniview/base-ui'; + +describe('TextField', () => { + benchRender('mount', () => ( + + Label + + + ), TIER_2_OPTIONS); + + benchRerender( + 'disabled toggle', + { initialProps: { disabled: false }, updatedProps: { disabled: true } }, + (props) => ( + + Label + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Timeline.bench.tsx b/packages/benchmarks/src/base-ui/Timeline.bench.tsx new file mode 100644 index 0000000..dc75c6c --- /dev/null +++ b/packages/benchmarks/src/base-ui/Timeline.bench.tsx @@ -0,0 +1,40 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Timeline } from '@omniview/base-ui'; +import type { ReactNode } from 'react'; + +const threeItems = ( + <> + Deployed v1.0 + Health check passed + Rollout complete + +); + +const fourItems = ( + <> + Deployed v1.0 + Health check passed + Rollout complete + Metrics stabilized + +); + +describe('Timeline', () => { + benchRender( + 'mount with 3 items', + () => {threeItems}, + TIER_2_OPTIONS, + ); + + benchRerender( + 'items change', + { + initialProps: { children: threeItems as ReactNode }, + updatedProps: { children: fourItems as ReactNode }, + }, + (props) => , + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Toast.bench.tsx b/packages/benchmarks/src/base-ui/Toast.bench.tsx new file mode 100644 index 0000000..ae654a7 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Toast.bench.tsx @@ -0,0 +1,30 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { ToastProvider } from '@omniview/base-ui'; + +describe('ToastProvider', () => { + benchRender( + 'mount provider', + () => ( + +
App content
+
+ ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'position change', + { + initialProps: { position: 'bottom-right' as const }, + updatedProps: { position: 'top-left' as const }, + }, + (props) => ( + +
App content
+
+ ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx b/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx new file mode 100644 index 0000000..b98f368 --- /dev/null +++ b/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx @@ -0,0 +1,26 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { ToggleButton } from '@omniview/base-ui'; + +describe('ToggleButton', () => { + benchRender( + 'mount', + () => Bold, + TIER_2_OPTIONS, + ); + + benchRerender( + 'pressed toggle', + { + initialProps: { pressed: false as boolean }, + updatedProps: { pressed: true as boolean }, + }, + (props) => ( + + Bold + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/ToggleButtonGroup.bench.tsx b/packages/benchmarks/src/base-ui/ToggleButtonGroup.bench.tsx new file mode 100644 index 0000000..d0b51bd --- /dev/null +++ b/packages/benchmarks/src/base-ui/ToggleButtonGroup.bench.tsx @@ -0,0 +1,34 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { ToggleButtonGroup } from '@omniview/base-ui'; + +describe('ToggleButtonGroup', () => { + benchRender( + 'mount with 3 items', + () => ( + + Bold + Italic + Underline + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'value change', + { + initialProps: { value: ['bold'] as string[] }, + updatedProps: { value: ['italic'] as string[] }, + }, + (props) => ( + + Bold + Italic + Underline + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Toolbar.bench.tsx b/packages/benchmarks/src/base-ui/Toolbar.bench.tsx new file mode 100644 index 0000000..ab1a044 --- /dev/null +++ b/packages/benchmarks/src/base-ui/Toolbar.bench.tsx @@ -0,0 +1,38 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Toolbar, Button } from '@omniview/base-ui'; + +describe('Toolbar', () => { + benchRender( + 'mount with buttons', + () => ( + + + + + + + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'disabled toggle', + { + initialProps: { 'aria-disabled': undefined as undefined }, + updatedProps: { 'aria-disabled': 'true' as const }, + }, + (props) => ( + + + + + + + + ), + TIER_2_OPTIONS, + ); +}); diff --git a/packages/benchmarks/src/base-ui/Tooltip.bench.tsx b/packages/benchmarks/src/base-ui/Tooltip.bench.tsx new file mode 100644 index 0000000..9ee5b5a --- /dev/null +++ b/packages/benchmarks/src/base-ui/Tooltip.bench.tsx @@ -0,0 +1,40 @@ +import { describe } from 'vitest'; +import { benchRender, benchRerender } from '../utils/bench-render'; +import { TIER_2_OPTIONS } from '../utils/bench-options'; +import { Tooltip } from '@omniview/base-ui'; + +describe('Tooltip', () => { + benchRender( + 'mount', + () => ( + + Hover me + + + Tooltip content + + + + ), + TIER_2_OPTIONS, + ); + + benchRerender( + 'open toggle', + { + initialProps: { open: false as boolean }, + updatedProps: { open: true as boolean }, + }, + (props) => ( + + Hover me + + + Tooltip content + + + + ), + TIER_2_OPTIONS, + ); +}); From 99d1f1912ed9bf1698eaa46a4682d3781ab36770 Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 02:35:25 -0500 Subject: [PATCH 09/13] fix(benchmarks): add Portal wrapper to Autocomplete and Combobox benchmarks --- .../src/base-ui/Autocomplete.bench.tsx | 24 ++++++++++--------- .../benchmarks/src/base-ui/Combobox.bench.tsx | 24 ++++++++++--------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx b/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx index 2d126d6..f9b4f44 100644 --- a/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx +++ b/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx @@ -13,17 +13,19 @@ function AutocompleteBench({ options }: { options: Option[] }) { - - - - {(item: Option) => ( - - {item.label} - - )} - - - + + + + + {(item: Option) => ( + + {item.label} + + )} + + + + ); } diff --git a/packages/benchmarks/src/base-ui/Combobox.bench.tsx b/packages/benchmarks/src/base-ui/Combobox.bench.tsx index 3e49011..c45b214 100644 --- a/packages/benchmarks/src/base-ui/Combobox.bench.tsx +++ b/packages/benchmarks/src/base-ui/Combobox.bench.tsx @@ -17,17 +17,19 @@ function ComboboxBench({ options }: { options: Option[] }) { > - - - - {(item: Option) => ( - - {item.label} - - )} - - - + + + + + {(item: Option) => ( + + {item.label} + + )} + + + + ); } From d66394fb3c5e3e0c1a820ebb1a7ebd14b15a3354 Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 03:12:43 -0500 Subject: [PATCH 10/13] fix(benchmarks): add Portal to AlertDialog and matchMedia stub for EditorTabs --- .../src/base-ui/AlertDialog.bench.tsx | 28 +++++++++++-------- packages/benchmarks/src/setup.ts | 13 +++++++++ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx b/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx index 02f2df1..a1ad1cb 100644 --- a/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx +++ b/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx @@ -8,12 +8,14 @@ describe('AlertDialog', () => { 'mount (open)', () => ( - - - Confirm - Are you sure? - Cancel - + + + + Confirm + Are you sure? + Cancel + + ), TIER_2_OPTIONS, @@ -27,12 +29,14 @@ describe('AlertDialog', () => { }, (props) => ( - - - Confirm - Are you sure? - Cancel - + + + + Confirm + Are you sure? + Cancel + + ), TIER_2_OPTIONS, diff --git a/packages/benchmarks/src/setup.ts b/packages/benchmarks/src/setup.ts index f1731ac..5f7aac7 100644 --- a/packages/benchmarks/src/setup.ts +++ b/packages/benchmarks/src/setup.ts @@ -11,6 +11,19 @@ if (typeof globalThis.ResizeObserver === 'undefined') { } as unknown as typeof ResizeObserver; } +if (typeof window !== 'undefined' && typeof window.matchMedia !== 'function') { + window.matchMedia = (query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + }) as MediaQueryList; +} + if (typeof globalThis.IntersectionObserver === 'undefined') { globalThis.IntersectionObserver = class IntersectionObserver { readonly root = null; From 867c3b8836095cd946e87d0ddbfbb234ad75a5e4 Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 12:19:15 -0500 Subject: [PATCH 11/13] perf(benchmarks): address PR review feedback and expand mountMany coverage - Remove unnecessary type casts (as boolean, as const, as undefined) across all benchmark files - Convert prebuilt JSX constants to factory functions (Breadcrumbs, DescriptionList, DockLayout, Timeline) - Extract shared helpers (EditableList threeItems, SearchInput noop) - Add defaultOpen to Combobox, animate=false to Drawer rerender - Rename Menu 'mount' to 'mount (closed)' for clarity - Reduce iteration counts (Tier 1: 30/3, Tier 2: 20/2) for faster local runs - Remove benchMountMany from heavy containers (Popover, DataTable, *List) - Reduce item counts from 500 to 200 for list benchmarks - Fix makeTreeNodes exponential growth with breadth-first budget approach - Add benchMountMany to 21 components commonly mounted many times in DOM: IconButton, Chip, Card, Switch, ToggleButton, Input, TextField, TextArea, NumberInput, FormField, Tooltip, StatRow, Progress, Meter, Spinner, ClipboardText, Collapsible, Radio, Banner, Select, Breadcrumbs --- .../src/base-ui/ActionList.bench.tsx | 4 +- .../src/base-ui/AlertDialog.bench.tsx | 4 +- .../benchmarks/src/base-ui/AppShell.bench.tsx | 4 +- .../benchmarks/src/base-ui/Banner.bench.tsx | 9 +- .../src/base-ui/BasicList.bench.tsx | 20 +--- .../src/base-ui/Breadcrumbs.bench.tsx | 50 +++++--- .../benchmarks/src/base-ui/Card.bench.tsx | 9 +- .../benchmarks/src/base-ui/Checkbox.bench.tsx | 2 +- .../benchmarks/src/base-ui/Chip.bench.tsx | 4 +- .../src/base-ui/ClipboardText.bench.tsx | 4 +- .../src/base-ui/Collapsible.bench.tsx | 13 ++- .../benchmarks/src/base-ui/Combobox.bench.tsx | 1 + .../src/base-ui/CommandList.bench.tsx | 20 +--- .../src/base-ui/ConfirmButton.bench.tsx | 4 +- .../src/base-ui/DataTable.bench.tsx | 17 +-- .../src/base-ui/DescriptionList.bench.tsx | 41 ++++--- .../benchmarks/src/base-ui/Dialog.bench.tsx | 4 +- .../src/base-ui/DockLayout.bench.tsx | 108 +++++++++--------- .../benchmarks/src/base-ui/Drawer.bench.tsx | 6 +- .../src/base-ui/EditableList.bench.tsx | 67 +++++------ .../src/base-ui/EditorTabs.bench.tsx | 17 +-- .../src/base-ui/FormField.bench.tsx | 8 +- .../src/base-ui/IconButton.bench.tsx | 4 +- .../benchmarks/src/base-ui/Input.bench.tsx | 9 +- .../benchmarks/src/base-ui/Menu.bench.tsx | 6 +- .../benchmarks/src/base-ui/Meter.bench.tsx | 4 +- .../src/base-ui/MultiSelect.bench.tsx | 9 +- .../benchmarks/src/base-ui/NavList.bench.tsx | 4 +- .../src/base-ui/NumberInput.bench.tsx | 13 ++- .../src/base-ui/Pagination.bench.tsx | 4 +- .../benchmarks/src/base-ui/Popover.bench.tsx | 19 +-- .../benchmarks/src/base-ui/Progress.bench.tsx | 4 +- .../benchmarks/src/base-ui/Radio.bench.tsx | 4 +- .../benchmarks/src/base-ui/RowList.bench.tsx | 20 +--- .../src/base-ui/SearchInput.bench.tsx | 6 +- .../benchmarks/src/base-ui/Select.bench.tsx | 21 +++- .../src/base-ui/SelectableList.bench.tsx | 20 +--- .../benchmarks/src/base-ui/Spinner.bench.tsx | 4 +- .../src/base-ui/SplitButton.bench.tsx | 4 +- .../benchmarks/src/base-ui/StatRow.bench.tsx | 9 +- .../benchmarks/src/base-ui/Switch.bench.tsx | 4 +- .../benchmarks/src/base-ui/TextArea.bench.tsx | 9 +- .../src/base-ui/TextField.bench.tsx | 9 +- .../benchmarks/src/base-ui/Timeline.bench.tsx | 41 ++++--- .../src/base-ui/ToggleButton.bench.tsx | 8 +- .../benchmarks/src/base-ui/Toolbar.bench.tsx | 4 +- .../benchmarks/src/base-ui/Tooltip.bench.tsx | 17 ++- .../benchmarks/src/base-ui/TreeList.bench.tsx | 10 +- .../benchmarks/src/utils/bench-options.ts | 8 +- packages/benchmarks/src/utils/factories.ts | 53 +++++++-- 50 files changed, 416 insertions(+), 327 deletions(-) diff --git a/packages/benchmarks/src/base-ui/ActionList.bench.tsx b/packages/benchmarks/src/base-ui/ActionList.bench.tsx index 5ade470..fd337b6 100644 --- a/packages/benchmarks/src/base-ui/ActionList.bench.tsx +++ b/packages/benchmarks/src/base-ui/ActionList.bench.tsx @@ -21,8 +21,8 @@ describe('ActionList', () => { benchRerender( 'disabled toggle', { - initialProps: { disabled: false as boolean }, - updatedProps: { disabled: true as boolean }, + initialProps: { disabled: false }, + updatedProps: { disabled: true }, }, (props) => ( diff --git a/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx b/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx index a1ad1cb..e49a80e 100644 --- a/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx +++ b/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx @@ -24,8 +24,8 @@ describe('AlertDialog', () => { benchRerender( 'open toggle', { - initialProps: { open: true as boolean }, - updatedProps: { open: false as boolean }, + initialProps: { open: true }, + updatedProps: { open: false }, }, (props) => ( diff --git a/packages/benchmarks/src/base-ui/AppShell.bench.tsx b/packages/benchmarks/src/base-ui/AppShell.bench.tsx index 1023e2b..60a8156 100644 --- a/packages/benchmarks/src/base-ui/AppShell.bench.tsx +++ b/packages/benchmarks/src/base-ui/AppShell.bench.tsx @@ -19,8 +19,8 @@ describe('AppShell', () => { benchRerender( 'sidebar visibility toggle', { - initialProps: { sidebarCollapsed: false as boolean }, - updatedProps: { sidebarCollapsed: true as boolean }, + initialProps: { sidebarCollapsed: false }, + updatedProps: { sidebarCollapsed: true }, }, (props) => ( diff --git a/packages/benchmarks/src/base-ui/Banner.bench.tsx b/packages/benchmarks/src/base-ui/Banner.bench.tsx index 3020014..4a480db 100644 --- a/packages/benchmarks/src/base-ui/Banner.bench.tsx +++ b/packages/benchmarks/src/base-ui/Banner.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Banner } from '@omniview/base-ui'; @@ -29,4 +29,11 @@ describe('Banner', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 50', 50, (i) => ( + + Notice {i} + Message {i} + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/BasicList.bench.tsx b/packages/benchmarks/src/base-ui/BasicList.bench.tsx index 60a7572..f33e372 100644 --- a/packages/benchmarks/src/base-ui/BasicList.bench.tsx +++ b/packages/benchmarks/src/base-ui/BasicList.bench.tsx @@ -1,13 +1,12 @@ import { describe } from 'vitest'; import { BasicList } from '@omniview/base-ui'; -import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_1_OPTIONS } from '../utils/bench-options'; import { makeRows, type Row } from '../utils/factories'; // Pre-generate data -const rows500 = makeRows(500); -const rows600 = makeRows(600); -const rows25 = makeRows(25); +const rows200 = makeRows(200); +const rows300 = makeRows(300); // --------------------------------------------------------------------------- // Wrapper @@ -33,19 +32,12 @@ function BasicListBench({ data }: { data: Row[] }) { // --------------------------------------------------------------------------- describe('BasicList', () => { - benchRender('mount 500 items', () => , TIER_1_OPTIONS); + benchRender('mount 200 items', () => , TIER_1_OPTIONS); benchRerender( - 'data change (500 -> 600 items)', - { initialProps: { data: rows500 }, updatedProps: { data: rows600 } }, + 'data change (200 -> 300 items)', + { initialProps: { data: rows200 }, updatedProps: { data: rows300 } }, (props) => , TIER_1_OPTIONS, ); - - benchMountMany( - 'mount 20 instances (25 items each)', - 20, - (i) => , - TIER_1_OPTIONS, - ); }); diff --git a/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx b/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx index 155ceee..c25600a 100644 --- a/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx +++ b/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx @@ -1,39 +1,51 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Breadcrumbs } from '@omniview/base-ui'; -const threeItems = ( - <> - Home - Docs - Current - -); +function threeItems() { + return ( + <> + Home + Docs + Current + + ); +} -const fourItems = ( - <> - Home - Docs - API - Current - -); +function fourItems() { + return ( + <> + Home + Docs + API + Current + + ); +} describe('Breadcrumbs', () => { benchRender( 'mount with 3 items', - () => {threeItems}, + () => {threeItems()}, TIER_2_OPTIONS, ); benchRerender( 'items change', { - initialProps: { children: threeItems }, - updatedProps: { children: fourItems }, + initialProps: { children: threeItems() }, + updatedProps: { children: fourItems() }, }, (props) => , TIER_2_OPTIONS, ); + + benchMountMany('mount 50', 50, (i) => ( + + Home + Section + Page {i} + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Card.bench.tsx b/packages/benchmarks/src/base-ui/Card.bench.tsx index 3101e83..5b8b8cc 100644 --- a/packages/benchmarks/src/base-ui/Card.bench.tsx +++ b/packages/benchmarks/src/base-ui/Card.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Card } from '@omniview/base-ui'; @@ -33,4 +33,11 @@ describe('Card', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 100', 100, (i) => ( + + Card {i} + Content {i} + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Checkbox.bench.tsx b/packages/benchmarks/src/base-ui/Checkbox.bench.tsx index 2814734..486d6aa 100644 --- a/packages/benchmarks/src/base-ui/Checkbox.bench.tsx +++ b/packages/benchmarks/src/base-ui/Checkbox.bench.tsx @@ -11,5 +11,5 @@ describe('Checkbox', () => { (props) => , TIER_2_OPTIONS, ); - benchMountMany('mount 1000', 1000, (i) => , TIER_2_OPTIONS); + benchMountMany('mount 200', 200, (i) => , TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Chip.bench.tsx b/packages/benchmarks/src/base-ui/Chip.bench.tsx index 807c443..c3459aa 100644 --- a/packages/benchmarks/src/base-ui/Chip.bench.tsx +++ b/packages/benchmarks/src/base-ui/Chip.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Chip } from '@omniview/base-ui'; @@ -19,4 +19,6 @@ describe('Chip', () => { (props) => Label, TIER_2_OPTIONS, ); + + benchMountMany('mount 200', 200, (i) => Tag {i}, TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx b/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx index 66f3356..b8f6113 100644 --- a/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx +++ b/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { ClipboardText } from '@omniview/base-ui'; @@ -19,4 +19,6 @@ describe('ClipboardText', () => { (props) => , TIER_2_OPTIONS, ); + + benchMountMany('mount 100', 100, (i) => , TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Collapsible.bench.tsx b/packages/benchmarks/src/base-ui/Collapsible.bench.tsx index f6411a6..6d652dd 100644 --- a/packages/benchmarks/src/base-ui/Collapsible.bench.tsx +++ b/packages/benchmarks/src/base-ui/Collapsible.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@omniview/base-ui'; @@ -18,8 +18,8 @@ describe('Collapsible', () => { benchRerender( 'open toggle', { - initialProps: { open: false as boolean }, - updatedProps: { open: true as boolean }, + initialProps: { open: false }, + updatedProps: { open: true }, }, (props) => ( @@ -29,4 +29,11 @@ describe('Collapsible', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 100', 100, (i) => ( + + Section {i} + Content {i} + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Combobox.bench.tsx b/packages/benchmarks/src/base-ui/Combobox.bench.tsx index c45b214..b94f8e3 100644 --- a/packages/benchmarks/src/base-ui/Combobox.bench.tsx +++ b/packages/benchmarks/src/base-ui/Combobox.bench.tsx @@ -14,6 +14,7 @@ function ComboboxBench({ options }: { options: Option[] }) { items={options} itemToStringLabel={(item: Option) => item.label} itemToStringValue={(item: Option) => item.value} + defaultOpen > diff --git a/packages/benchmarks/src/base-ui/CommandList.bench.tsx b/packages/benchmarks/src/base-ui/CommandList.bench.tsx index 5424ca3..dbba346 100644 --- a/packages/benchmarks/src/base-ui/CommandList.bench.tsx +++ b/packages/benchmarks/src/base-ui/CommandList.bench.tsx @@ -1,7 +1,7 @@ import { describe } from 'vitest'; import { CommandList } from '@omniview/base-ui'; import type { CommandItemMeta } from '@omniview/base-ui'; -import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_1_OPTIONS } from '../utils/bench-options'; import { makeCommandItems, type CommandItem } from '../utils/factories'; @@ -18,9 +18,8 @@ const renderItem = (item: CommandItem, meta: CommandItemMeta) => ( ); // Pre-generate data -const items500 = makeCommandItems(500); -const items600 = makeCommandItems(600); -const items25 = makeCommandItems(25); +const items200 = makeCommandItems(200); +const items300 = makeCommandItems(300); // --------------------------------------------------------------------------- // Wrapper @@ -44,19 +43,12 @@ function CommandListBench({ items }: { items: CommandItem[] }) { // --------------------------------------------------------------------------- describe('CommandList', () => { - benchRender('mount 500 items', () => , TIER_1_OPTIONS); + benchRender('mount 200 items', () => , TIER_1_OPTIONS); benchRerender( - 'data change (500 -> 600 items)', - { initialProps: { items: items500 }, updatedProps: { items: items600 } }, + 'data change (200 -> 300 items)', + { initialProps: { items: items200 }, updatedProps: { items: items300 } }, (props) => , TIER_1_OPTIONS, ); - - benchMountMany( - 'mount 20 instances (25 items each)', - 20, - (i) => , - TIER_1_OPTIONS, - ); }); diff --git a/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx b/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx index 7953179..9420a2d 100644 --- a/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx +++ b/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx @@ -15,8 +15,8 @@ describe('ConfirmButton', () => { benchRerender( 'disabled toggle', { - initialProps: { disabled: false as boolean, onConfirm: noop, children: 'Delete' as const }, - updatedProps: { disabled: true as boolean, onConfirm: noop, children: 'Delete' as const }, + initialProps: { disabled: false, onConfirm: noop, children: 'Delete' }, + updatedProps: { disabled: true, onConfirm: noop, children: 'Delete' }, }, (props) => , TIER_2_OPTIONS, diff --git a/packages/benchmarks/src/base-ui/DataTable.bench.tsx b/packages/benchmarks/src/base-ui/DataTable.bench.tsx index b422628..e294402 100644 --- a/packages/benchmarks/src/base-ui/DataTable.bench.tsx +++ b/packages/benchmarks/src/base-ui/DataTable.bench.tsx @@ -6,7 +6,7 @@ import { type ColumnDef, } from '@tanstack/react-table'; import { DataTable } from '@omniview/base-ui'; -import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_1_OPTIONS } from '../utils/bench-options'; import { makeRows, type Row } from '../utils/factories'; @@ -20,8 +20,7 @@ const columns: ColumnDef[] = [ ]; const rows100 = makeRows(100); -const rows500 = makeRows(500); -const rows600 = makeRows(600); +const rows200 = makeRows(200); function DataTableBench({ data }: { data: Row[] }) { const table = useReactTable({ @@ -42,19 +41,11 @@ function DataTableBench({ data }: { data: Row[] }) { describe('DataTable', () => { benchRender('mount 100 rows', () => , TIER_1_OPTIONS); - benchRender('mount 500 rows', () => , TIER_1_OPTIONS); benchRerender( - 'data change (500 → 600 rows)', - { initialProps: { data: rows500 }, updatedProps: { data: rows600 } }, + 'data change (100 → 200 rows)', + { initialProps: { data: rows100 }, updatedProps: { data: rows200 } }, (props) => , TIER_1_OPTIONS, ); - - benchMountMany( - 'mount 50 tables (10 rows each)', - 50, - (i) => , - TIER_1_OPTIONS, - ); }); diff --git a/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx b/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx index 0bfbd0c..9cf5f97 100644 --- a/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx +++ b/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx @@ -2,37 +2,40 @@ import { describe } from 'vitest'; import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { DescriptionList } from '@omniview/base-ui'; -import type { ReactNode } from 'react'; -const threeItems = ( - <> - Widget - Active - 1.0.0 - -); +function threeItems() { + return ( + <> + Widget + Active + 1.0.0 + + ); +} -const fourItems = ( - <> - Widget - Active - 1.0.0 - US-East - -); +function fourItems() { + return ( + <> + Widget + Active + 1.0.0 + US-East + + ); +} describe('DescriptionList', () => { benchRender( 'mount with 3 items', - () => {threeItems}, + () => {threeItems()}, TIER_2_OPTIONS, ); benchRerender( 'items change', { - initialProps: { children: threeItems as ReactNode }, - updatedProps: { children: fourItems as ReactNode }, + initialProps: { children: threeItems() }, + updatedProps: { children: fourItems() }, }, (props) => , TIER_2_OPTIONS, diff --git a/packages/benchmarks/src/base-ui/Dialog.bench.tsx b/packages/benchmarks/src/base-ui/Dialog.bench.tsx index 854df68..25f5c36 100644 --- a/packages/benchmarks/src/base-ui/Dialog.bench.tsx +++ b/packages/benchmarks/src/base-ui/Dialog.bench.tsx @@ -24,8 +24,8 @@ describe('Dialog', () => { benchRerender( 'open/close toggle', { - initialProps: { open: true as boolean }, - updatedProps: { open: false as boolean }, + initialProps: { open: true }, + updatedProps: { open: false }, }, (props) => ( diff --git a/packages/benchmarks/src/base-ui/DockLayout.bench.tsx b/packages/benchmarks/src/base-ui/DockLayout.bench.tsx index 2d97cd3..ef4dd34 100644 --- a/packages/benchmarks/src/base-ui/DockLayout.bench.tsx +++ b/packages/benchmarks/src/base-ui/DockLayout.bench.tsx @@ -3,69 +3,73 @@ import { benchRender, benchRerender, benchMountMany } from '../utils/bench-rende import { TIER_1_OPTIONS } from '../utils/bench-options'; import { DockLayout, type DockNode } from '@omniview/base-ui'; -const twoPanel: DockNode = { - type: 'split', - direction: 'horizontal', - children: [ - { - type: 'leaf', - id: 'left', - tabs: [ - { id: 'tab-1', title: 'File A', content:
Content A
}, - { id: 'tab-2', title: 'File B', content:
Content B
}, - ], - activeTab: 'tab-1', - }, - { - type: 'leaf', - id: 'right', - tabs: [ - { id: 'tab-3', title: 'File C', content:
Content C
}, - ], - activeTab: 'tab-3', - }, - ], - sizes: [1, 1], -}; +function makeTwoPanel(): DockNode { + return { + type: 'split', + direction: 'horizontal', + children: [ + { + type: 'leaf', + id: 'left', + tabs: [ + { id: 'tab-1', title: 'File A', content:
Content A
}, + { id: 'tab-2', title: 'File B', content:
Content B
}, + ], + activeTab: 'tab-1', + }, + { + type: 'leaf', + id: 'right', + tabs: [ + { id: 'tab-3', title: 'File C', content:
Content C
}, + ], + activeTab: 'tab-3', + }, + ], + sizes: [1, 1], + }; +} -const threePanelVertical: DockNode = { - type: 'split', - direction: 'vertical', - children: [ - { - type: 'leaf', - id: 'top', - tabs: [{ id: 'tab-a', title: 'Top', content:
Top
}], - activeTab: 'tab-a', - }, - { - type: 'leaf', - id: 'middle', - tabs: [{ id: 'tab-b', title: 'Middle', content:
Middle
}], - activeTab: 'tab-b', - }, - { - type: 'leaf', - id: 'bottom', - tabs: [{ id: 'tab-c', title: 'Bottom', content:
Bottom
}], - activeTab: 'tab-c', - }, - ], - sizes: [1, 1, 1], -}; +function makeThreePanelVertical(): DockNode { + return { + type: 'split', + direction: 'vertical', + children: [ + { + type: 'leaf', + id: 'top', + tabs: [{ id: 'tab-a', title: 'Top', content:
Top
}], + activeTab: 'tab-a', + }, + { + type: 'leaf', + id: 'middle', + tabs: [{ id: 'tab-b', title: 'Middle', content:
Middle
}], + activeTab: 'tab-b', + }, + { + type: 'leaf', + id: 'bottom', + tabs: [{ id: 'tab-c', title: 'Bottom', content:
Bottom
}], + activeTab: 'tab-c', + }, + ], + sizes: [1, 1, 1], + }; +} describe('DockLayout', () => { benchRender( 'mount 2-panel layout', - () => , + () => , TIER_1_OPTIONS, ); benchRerender( 'layout change (2-panel → 3-panel vertical)', { - initialProps: { layout: twoPanel }, - updatedProps: { layout: threePanelVertical }, + initialProps: { layout: makeTwoPanel() }, + updatedProps: { layout: makeThreePanelVertical() }, }, (props) => , TIER_1_OPTIONS, diff --git a/packages/benchmarks/src/base-ui/Drawer.bench.tsx b/packages/benchmarks/src/base-ui/Drawer.bench.tsx index 423e438..4edeffe 100644 --- a/packages/benchmarks/src/base-ui/Drawer.bench.tsx +++ b/packages/benchmarks/src/base-ui/Drawer.bench.tsx @@ -19,11 +19,11 @@ describe('Drawer', () => { benchRerender( 'open/close toggle', { - initialProps: { open: true as boolean }, - updatedProps: { open: false as boolean }, + initialProps: { open: true }, + updatedProps: { open: false }, }, (props) => ( - + Drawer content ), diff --git a/packages/benchmarks/src/base-ui/EditableList.bench.tsx b/packages/benchmarks/src/base-ui/EditableList.bench.tsx index 98f169f..ed50815 100644 --- a/packages/benchmarks/src/base-ui/EditableList.bench.tsx +++ b/packages/benchmarks/src/base-ui/EditableList.bench.tsx @@ -5,29 +5,37 @@ import { EditableList } from '@omniview/base-ui'; const noop = () => {}; +function threeItems() { + return ( + <> + + Item A + + + + + + Item B + + + + + + Item C + + + + + + ); +} + describe('EditableList', () => { benchRender( 'mount with items', () => ( - - Item A - - - - - - Item B - - - - - - Item C - - - - + {threeItems()} ), TIER_2_OPTIONS, @@ -36,29 +44,12 @@ describe('EditableList', () => { benchRerender( 'editable toggle', { - initialProps: { editable: true as boolean }, - updatedProps: { editable: false as boolean }, + initialProps: { editable: true }, + updatedProps: { editable: false }, }, (props) => ( - - Item A - - - - - - Item B - - - - - - Item C - - - - + {threeItems()} ), TIER_2_OPTIONS, diff --git a/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx b/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx index cf11fb5..fdfff59 100644 --- a/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx +++ b/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx @@ -1,18 +1,11 @@ import { describe } from 'vitest'; import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_1_OPTIONS } from '../utils/bench-options'; -import { EditorTabs, type TabDescriptor } from '@omniview/base-ui'; +import { EditorTabs } from '@omniview/base-ui'; +import { makeEditorTabs } from '../utils/factories'; -function makeTabs(count: number, prefix = ''): TabDescriptor[] { - return Array.from({ length: count }, (_, i) => ({ - id: `${prefix}tab-${i}`, - title: `File ${prefix}${i}.ts`, - closable: true, - })); -} - -const tabs20 = makeTabs(20); -const tabs40 = makeTabs(40); +const tabs20 = makeEditorTabs(20); +const tabs40 = makeEditorTabs(40); describe('EditorTabs', () => { benchRender( @@ -35,7 +28,7 @@ describe('EditorTabs', () => { 'mount 30 tab bars (5 tabs each)', 30, (i) => { - const tabs = makeTabs(5, `bar${i}-`); + const tabs = makeEditorTabs(5, `bar${i}-`); return ; }, TIER_1_OPTIONS, diff --git a/packages/benchmarks/src/base-ui/FormField.bench.tsx b/packages/benchmarks/src/base-ui/FormField.bench.tsx index 6bf2ce1..83d9d1a 100644 --- a/packages/benchmarks/src/base-ui/FormField.bench.tsx +++ b/packages/benchmarks/src/base-ui/FormField.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { FormField } from '@omniview/base-ui'; @@ -23,4 +23,10 @@ describe('FormField', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 100', 100, (i) => ( + + + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/IconButton.bench.tsx b/packages/benchmarks/src/base-ui/IconButton.bench.tsx index 0eb4761..113384f 100644 --- a/packages/benchmarks/src/base-ui/IconButton.bench.tsx +++ b/packages/benchmarks/src/base-ui/IconButton.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { IconButton } from '@omniview/base-ui'; @@ -19,4 +19,6 @@ describe('IconButton', () => { (props) => E, TIER_2_OPTIONS, ); + + benchMountMany('mount 200', 200, (i) => E, TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Input.bench.tsx b/packages/benchmarks/src/base-ui/Input.bench.tsx index b2658a9..b8a3663 100644 --- a/packages/benchmarks/src/base-ui/Input.bench.tsx +++ b/packages/benchmarks/src/base-ui/Input.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Input } from '@omniview/base-ui'; @@ -22,4 +22,11 @@ describe('Input', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 100', 100, (i) => ( + + Field {i} + + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Menu.bench.tsx b/packages/benchmarks/src/base-ui/Menu.bench.tsx index a7d4ece..163edd5 100644 --- a/packages/benchmarks/src/base-ui/Menu.bench.tsx +++ b/packages/benchmarks/src/base-ui/Menu.bench.tsx @@ -5,7 +5,7 @@ import { Menu } from '@omniview/base-ui'; describe('Menu', () => { benchRender( - 'mount', + 'mount (closed)', () => ( Open @@ -26,8 +26,8 @@ describe('Menu', () => { benchRerender( 'open toggle', { - initialProps: { open: false as boolean }, - updatedProps: { open: true as boolean }, + initialProps: { open: false }, + updatedProps: { open: true }, }, (props) => ( diff --git a/packages/benchmarks/src/base-ui/Meter.bench.tsx b/packages/benchmarks/src/base-ui/Meter.bench.tsx index 7431f71..ac0830a 100644 --- a/packages/benchmarks/src/base-ui/Meter.bench.tsx +++ b/packages/benchmarks/src/base-ui/Meter.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Meter } from '@omniview/base-ui'; @@ -19,4 +19,6 @@ describe('Meter', () => { (props) => , TIER_2_OPTIONS, ); + + benchMountMany('mount 100', 100, (i) => , TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx b/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx index 5dc481f..404ce2e 100644 --- a/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx +++ b/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx @@ -1,11 +1,10 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_1_OPTIONS } from '../utils/bench-options'; import { makeOptions, type Option } from '../utils/factories'; import { MultiSelect } from '@omniview/base-ui'; const options100 = makeOptions(100); -const options10 = makeOptions(10); const noSelection: Option[] = []; const selected20 = options100.slice(0, 20); @@ -38,10 +37,4 @@ describe('MultiSelect', () => { TIER_1_OPTIONS, ); - benchMountMany( - 'mount 100 instances (10 options each)', - 100, - (i) => , - TIER_1_OPTIONS, - ); }); diff --git a/packages/benchmarks/src/base-ui/NavList.bench.tsx b/packages/benchmarks/src/base-ui/NavList.bench.tsx index f3908be..eed4e65 100644 --- a/packages/benchmarks/src/base-ui/NavList.bench.tsx +++ b/packages/benchmarks/src/base-ui/NavList.bench.tsx @@ -25,8 +25,8 @@ describe('NavList', () => { benchRerender( 'active item change', { - initialProps: { activeKey: 'home' as string }, - updatedProps: { activeKey: 'settings' as string }, + initialProps: { activeKey: 'home' }, + updatedProps: { activeKey: 'settings' }, }, (props) => ( diff --git a/packages/benchmarks/src/base-ui/NumberInput.bench.tsx b/packages/benchmarks/src/base-ui/NumberInput.bench.tsx index e7d0ac4..bc4994d 100644 --- a/packages/benchmarks/src/base-ui/NumberInput.bench.tsx +++ b/packages/benchmarks/src/base-ui/NumberInput.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { NumberInput } from '@omniview/base-ui'; @@ -30,4 +30,15 @@ describe('NumberInput', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 50', 50, (i) => ( + + Qty {i} + + + + + + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Pagination.bench.tsx b/packages/benchmarks/src/base-ui/Pagination.bench.tsx index 49bf209..6fde980 100644 --- a/packages/benchmarks/src/base-ui/Pagination.bench.tsx +++ b/packages/benchmarks/src/base-ui/Pagination.bench.tsx @@ -15,8 +15,8 @@ describe('Pagination', () => { benchRerender( 'page change', { - initialProps: { page: 1 as number }, - updatedProps: { page: 5 as number }, + initialProps: { page: 1 }, + updatedProps: { page: 5 }, }, (props) => , TIER_2_OPTIONS, diff --git a/packages/benchmarks/src/base-ui/Popover.bench.tsx b/packages/benchmarks/src/base-ui/Popover.bench.tsx index 15629fe..8cf374d 100644 --- a/packages/benchmarks/src/base-ui/Popover.bench.tsx +++ b/packages/benchmarks/src/base-ui/Popover.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_1_OPTIONS } from '../utils/bench-options'; import { Popover } from '@omniview/base-ui'; @@ -45,21 +45,4 @@ describe('Popover', () => { TIER_1_OPTIONS, ); - benchMountMany( - 'mount 50 popovers', - 50, - (i) => ( - - Trigger {i} - - - - Content {i} - - - - - ), - TIER_1_OPTIONS, - ); }); diff --git a/packages/benchmarks/src/base-ui/Progress.bench.tsx b/packages/benchmarks/src/base-ui/Progress.bench.tsx index b274f5a..d96ea5d 100644 --- a/packages/benchmarks/src/base-ui/Progress.bench.tsx +++ b/packages/benchmarks/src/base-ui/Progress.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Progress } from '@omniview/base-ui'; @@ -19,4 +19,6 @@ describe('Progress', () => { (props) => , TIER_2_OPTIONS, ); + + benchMountMany('mount 200', 200, (i) => , TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Radio.bench.tsx b/packages/benchmarks/src/base-ui/Radio.bench.tsx index 2c989c4..761abfe 100644 --- a/packages/benchmarks/src/base-ui/Radio.bench.tsx +++ b/packages/benchmarks/src/base-ui/Radio.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Radio } from '@omniview/base-ui'; @@ -16,4 +16,6 @@ describe('Radio', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 200', 200, (i) => Option {i}, TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/RowList.bench.tsx b/packages/benchmarks/src/base-ui/RowList.bench.tsx index 4f4d9e3..ee89a35 100644 --- a/packages/benchmarks/src/base-ui/RowList.bench.tsx +++ b/packages/benchmarks/src/base-ui/RowList.bench.tsx @@ -1,7 +1,7 @@ import { describe } from 'vitest'; import { RowList } from '@omniview/base-ui'; import type { ColumnDef } from '@omniview/base-ui'; -import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_1_OPTIONS } from '../utils/bench-options'; import { makeRows, type Row } from '../utils/factories'; @@ -17,9 +17,8 @@ const columns: ColumnDef[] = [ ]; // Pre-generate data -const rows500 = makeRows(500); -const rows600 = makeRows(600); -const rows25 = makeRows(25); +const rows200 = makeRows(200); +const rows300 = makeRows(300); // --------------------------------------------------------------------------- // Wrapper @@ -48,19 +47,12 @@ function RowListBench({ data }: { data: Row[] }) { // --------------------------------------------------------------------------- describe('RowList', () => { - benchRender('mount 500 rows', () => , TIER_1_OPTIONS); + benchRender('mount 200 rows', () => , TIER_1_OPTIONS); benchRerender( - 'data change (500 -> 600 rows)', - { initialProps: { data: rows500 }, updatedProps: { data: rows600 } }, + 'data change (200 -> 300 rows)', + { initialProps: { data: rows200 }, updatedProps: { data: rows300 } }, (props) => , TIER_1_OPTIONS, ); - - benchMountMany( - 'mount 20 instances (25 rows each)', - 20, - (i) => , - TIER_1_OPTIONS, - ); }); diff --git a/packages/benchmarks/src/base-ui/SearchInput.bench.tsx b/packages/benchmarks/src/base-ui/SearchInput.bench.tsx index 85dc177..1d999ef 100644 --- a/packages/benchmarks/src/base-ui/SearchInput.bench.tsx +++ b/packages/benchmarks/src/base-ui/SearchInput.bench.tsx @@ -3,16 +3,18 @@ import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { SearchInput } from '@omniview/base-ui'; +const noop = () => {}; + describe('SearchInput', () => { benchRender('mount', () => ( - {}} /> + ), TIER_2_OPTIONS); benchRerender( 'value change', { initialProps: { value: '' }, updatedProps: { value: 'search query' } }, (props) => ( - {}} {...props} /> + ), TIER_2_OPTIONS, ); diff --git a/packages/benchmarks/src/base-ui/Select.bench.tsx b/packages/benchmarks/src/base-ui/Select.bench.tsx index 93e650a..d4e26ba 100644 --- a/packages/benchmarks/src/base-ui/Select.bench.tsx +++ b/packages/benchmarks/src/base-ui/Select.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { makeOptions } from '../utils/factories'; import { Select } from '@omniview/base-ui'; @@ -53,4 +53,23 @@ describe('Select', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 50', 50, (i) => ( + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/SelectableList.bench.tsx b/packages/benchmarks/src/base-ui/SelectableList.bench.tsx index 190fbeb..0e9bcab 100644 --- a/packages/benchmarks/src/base-ui/SelectableList.bench.tsx +++ b/packages/benchmarks/src/base-ui/SelectableList.bench.tsx @@ -1,12 +1,11 @@ import { describe } from 'vitest'; import { SelectableList } from '@omniview/base-ui'; -import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_1_OPTIONS } from '../utils/bench-options'; import { makeRows, type Row } from '../utils/factories'; // Pre-generate data -const rows500 = makeRows(500); -const rows25 = makeRows(25); +const rows200 = makeRows(200); // Pre-compute selection sets const noSelection = new Set(); @@ -44,25 +43,18 @@ function SelectableListBench({ describe('SelectableList', () => { benchRender( - 'mount 500 items', - () => , + 'mount 200 items', + () => , TIER_1_OPTIONS, ); benchRerender( 'selection change (0 -> 50 selected)', { - initialProps: { data: rows500, selectedKeys: noSelection }, - updatedProps: { data: rows500, selectedKeys: fiftySelected }, + initialProps: { data: rows200, selectedKeys: noSelection }, + updatedProps: { data: rows200, selectedKeys: fiftySelected }, }, (props) => , TIER_1_OPTIONS, ); - - benchMountMany( - 'mount 20 instances (25 items each)', - 20, - (i) => , - TIER_1_OPTIONS, - ); }); diff --git a/packages/benchmarks/src/base-ui/Spinner.bench.tsx b/packages/benchmarks/src/base-ui/Spinner.bench.tsx index db2c496..b7a51b6 100644 --- a/packages/benchmarks/src/base-ui/Spinner.bench.tsx +++ b/packages/benchmarks/src/base-ui/Spinner.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Spinner } from '@omniview/base-ui'; @@ -19,4 +19,6 @@ describe('Spinner', () => { (props) => , TIER_2_OPTIONS, ); + + benchMountMany('mount 200', 200, (i) => , TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/SplitButton.bench.tsx b/packages/benchmarks/src/base-ui/SplitButton.bench.tsx index 3d5a999..71b3e40 100644 --- a/packages/benchmarks/src/base-ui/SplitButton.bench.tsx +++ b/packages/benchmarks/src/base-ui/SplitButton.bench.tsx @@ -20,8 +20,8 @@ describe('SplitButton', () => { benchRerender( 'disabled toggle', { - initialProps: { disabled: false as boolean }, - updatedProps: { disabled: true as boolean }, + initialProps: { disabled: false }, + updatedProps: { disabled: true }, }, (props) => ( diff --git a/packages/benchmarks/src/base-ui/StatRow.bench.tsx b/packages/benchmarks/src/base-ui/StatRow.bench.tsx index 1d4c3c8..bea684c 100644 --- a/packages/benchmarks/src/base-ui/StatRow.bench.tsx +++ b/packages/benchmarks/src/base-ui/StatRow.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { StatRow } from '@omniview/base-ui'; import type { ReactNode } from 'react'; @@ -36,4 +36,11 @@ describe('StatRow', () => { (props) => , TIER_2_OPTIONS, ); + + benchMountMany('mount 100', 100, (i) => ( + + CPU: {i}% + Mem: {i} GB + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Switch.bench.tsx b/packages/benchmarks/src/base-ui/Switch.bench.tsx index def7008..8670c97 100644 --- a/packages/benchmarks/src/base-ui/Switch.bench.tsx +++ b/packages/benchmarks/src/base-ui/Switch.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Switch } from '@omniview/base-ui'; @@ -16,4 +16,6 @@ describe('Switch', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 200', 200, (i) => Setting {i}, TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/TextArea.bench.tsx b/packages/benchmarks/src/base-ui/TextArea.bench.tsx index 2ae331d..4aca204 100644 --- a/packages/benchmarks/src/base-ui/TextArea.bench.tsx +++ b/packages/benchmarks/src/base-ui/TextArea.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { TextArea } from '@omniview/base-ui'; @@ -22,4 +22,11 @@ describe('TextArea', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 50', 50, (i) => ( + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/TextField.bench.tsx b/packages/benchmarks/src/base-ui/TextField.bench.tsx index 616a614..755f0d5 100644 --- a/packages/benchmarks/src/base-ui/TextField.bench.tsx +++ b/packages/benchmarks/src/base-ui/TextField.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { TextField } from '@omniview/base-ui'; @@ -22,4 +22,11 @@ describe('TextField', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 100', 100, (i) => ( + + Field {i} + + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Timeline.bench.tsx b/packages/benchmarks/src/base-ui/Timeline.bench.tsx index dc75c6c..aa7d59a 100644 --- a/packages/benchmarks/src/base-ui/Timeline.bench.tsx +++ b/packages/benchmarks/src/base-ui/Timeline.bench.tsx @@ -2,37 +2,40 @@ import { describe } from 'vitest'; import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Timeline } from '@omniview/base-ui'; -import type { ReactNode } from 'react'; -const threeItems = ( - <> - Deployed v1.0 - Health check passed - Rollout complete - -); +function threeItems() { + return ( + <> + Deployed v1.0 + Health check passed + Rollout complete + + ); +} -const fourItems = ( - <> - Deployed v1.0 - Health check passed - Rollout complete - Metrics stabilized - -); +function fourItems() { + return ( + <> + Deployed v1.0 + Health check passed + Rollout complete + Metrics stabilized + + ); +} describe('Timeline', () => { benchRender( 'mount with 3 items', - () => {threeItems}, + () => {threeItems()}, TIER_2_OPTIONS, ); benchRerender( 'items change', { - initialProps: { children: threeItems as ReactNode }, - updatedProps: { children: fourItems as ReactNode }, + initialProps: { children: threeItems() }, + updatedProps: { children: fourItems() }, }, (props) => , TIER_2_OPTIONS, diff --git a/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx b/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx index b98f368..bd2491c 100644 --- a/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx +++ b/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { ToggleButton } from '@omniview/base-ui'; @@ -13,8 +13,8 @@ describe('ToggleButton', () => { benchRerender( 'pressed toggle', { - initialProps: { pressed: false as boolean }, - updatedProps: { pressed: true as boolean }, + initialProps: { pressed: false }, + updatedProps: { pressed: true }, }, (props) => ( @@ -23,4 +23,6 @@ describe('ToggleButton', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 200', 200, (i) => Option {i}, TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/Toolbar.bench.tsx b/packages/benchmarks/src/base-ui/Toolbar.bench.tsx index ab1a044..e99a69b 100644 --- a/packages/benchmarks/src/base-ui/Toolbar.bench.tsx +++ b/packages/benchmarks/src/base-ui/Toolbar.bench.tsx @@ -21,8 +21,8 @@ describe('Toolbar', () => { benchRerender( 'disabled toggle', { - initialProps: { 'aria-disabled': undefined as undefined }, - updatedProps: { 'aria-disabled': 'true' as const }, + initialProps: { 'aria-disabled': undefined }, + updatedProps: { 'aria-disabled': 'true' }, }, (props) => ( diff --git a/packages/benchmarks/src/base-ui/Tooltip.bench.tsx b/packages/benchmarks/src/base-ui/Tooltip.bench.tsx index 9ee5b5a..7afabd2 100644 --- a/packages/benchmarks/src/base-ui/Tooltip.bench.tsx +++ b/packages/benchmarks/src/base-ui/Tooltip.bench.tsx @@ -1,5 +1,5 @@ import { describe } from 'vitest'; -import { benchRender, benchRerender } from '../utils/bench-render'; +import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; import { TIER_2_OPTIONS } from '../utils/bench-options'; import { Tooltip } from '@omniview/base-ui'; @@ -22,8 +22,8 @@ describe('Tooltip', () => { benchRerender( 'open toggle', { - initialProps: { open: false as boolean }, - updatedProps: { open: true as boolean }, + initialProps: { open: false }, + updatedProps: { open: true }, }, (props) => ( @@ -37,4 +37,15 @@ describe('Tooltip', () => { ), TIER_2_OPTIONS, ); + + benchMountMany('mount 100', 100, (i) => ( + + Item {i} + + + Tooltip {i} + + + + ), TIER_2_OPTIONS); }); diff --git a/packages/benchmarks/src/base-ui/TreeList.bench.tsx b/packages/benchmarks/src/base-ui/TreeList.bench.tsx index 2fec28d..0d781d4 100644 --- a/packages/benchmarks/src/base-ui/TreeList.bench.tsx +++ b/packages/benchmarks/src/base-ui/TreeList.bench.tsx @@ -1,7 +1,7 @@ import { describe } from 'vitest'; import { TreeList } from '@omniview/base-ui'; import type { TreeNodeMeta } from '@omniview/base-ui'; -import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render'; +import { benchRender, benchRerender } from '../utils/bench-render'; import { TIER_1_OPTIONS } from '../utils/bench-options'; // --------------------------------------------------------------------------- @@ -49,8 +49,6 @@ const flat500 = makeFlatItems(500); const nested50x2 = makeNestedItems(50, 2); const flat100 = makeFlatItems(100); const flat150 = makeFlatItems(150); -const flat25 = makeFlatItems(25); - // --------------------------------------------------------------------------- // Wrapper // --------------------------------------------------------------------------- @@ -90,10 +88,4 @@ describe('TreeList', () => { TIER_1_OPTIONS, ); - benchMountMany( - 'mount 20 instances (25 nodes each)', - 20, - (i) => , - TIER_1_OPTIONS, - ); }); diff --git a/packages/benchmarks/src/utils/bench-options.ts b/packages/benchmarks/src/utils/bench-options.ts index f5495b7..a0fa345 100644 --- a/packages/benchmarks/src/utils/bench-options.ts +++ b/packages/benchmarks/src/utils/bench-options.ts @@ -16,12 +16,12 @@ export function resolveOptions(options?: BenchRenderOptions): BenchRenderOptions /** Tier 1: deep benchmarks (mount + rerender + mountMany). */ export const TIER_1_OPTIONS: BenchRenderOptions = { - iterations: 100, - warmupIterations: 5, + iterations: 30, + warmupIterations: 3, }; /** Tier 2: light benchmarks (mount + rerender only). */ export const TIER_2_OPTIONS: BenchRenderOptions = { - iterations: 50, - warmupIterations: 3, + iterations: 20, + warmupIterations: 2, }; diff --git a/packages/benchmarks/src/utils/factories.ts b/packages/benchmarks/src/utils/factories.ts index fa38261..6a5fada 100644 --- a/packages/benchmarks/src/utils/factories.ts +++ b/packages/benchmarks/src/utils/factories.ts @@ -30,14 +30,39 @@ export interface TreeNode { } export function makeTreeNodes(count: number, depth: number = 2): TreeNode[] { - function build(prefix: string, remaining: number): TreeNode[] { - return Array.from({ length: count }, (_, i) => ({ - id: `${prefix}-${i}`, - label: `Node ${prefix}-${i}`, - children: remaining > 0 ? build(`${prefix}-${i}`, remaining - 1) : undefined, - })); + let created = 0; + const roots: TreeNode[] = []; + const queue: { node: TreeNode; depth: number }[] = []; + + // Create root-level nodes + while (created < count) { + const node: TreeNode = { + id: `node-${created}`, + label: `Node ${created}`, + }; + created++; + roots.push(node); + if (depth > 0) queue.push({ node, depth: 1 }); } - return build('root', depth); + + // Breadth-first: attach children until budget exhausted + while (queue.length > 0 && created < count) { + const { node, depth: d } = queue.shift()!; + const children: TreeNode[] = []; + const childCount = Math.min(2, count - created); + for (let i = 0; i < childCount; i++) { + const child: TreeNode = { + id: `node-${created}`, + label: `Node ${created}`, + }; + created++; + children.push(child); + if (d < depth) queue.push({ node: child, depth: d + 1 }); + } + node.children = children; + } + + return roots; } // ── Option data (Select, Autocomplete, Combobox, MultiSelect) ── @@ -111,3 +136,17 @@ export function makeTabs(count: number): TabItem[] { closable: true, })); } + +export interface EditorTabItem { + id: string; + title: string; + closable?: boolean; +} + +export function makeEditorTabs(count: number, prefix = ''): EditorTabItem[] { + return Array.from({ length: count }, (_, i) => ({ + id: `${prefix}tab-${i}`, + title: `File ${prefix}${i}.ts`, + closable: true, + })); +} From 460a6ae7a7ad3a2a6c62b79022cbe86d118ae4e6 Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 14:12:16 -0500 Subject: [PATCH 12/13] feat(benchmarks): add Reassure render-count profiling infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Reassure-based performance tests that measure React render counts and durations per interaction scenario — the dimension our vitest bench microbenchmarks don't cover. Infrastructure: - vitest.config.perf.ts with JIT-disabled V8 flags for stable measurement - setup-perf.ts configures Reassure for @testing-library/react (20 runs, 3 warmup) - scripts/perf-compare.mjs for cross-branch comparison reporting - PointerEvent jsdom stub (required by @base-ui/react click handlers) Initial perf tests (7 files, 18 scenarios): - Button: mount baseline (1 render, 0 issues) - Checkbox: mount, toggle, toggle-one-in-list-of-20 - Select: mount with 20 options, open dropdown - Tabs: mount 5 tabs, switch active tab - Accordion: mount 5 items, expand, expand+collapse - Dialog: mount closed, open, open+close - Input/TextField: mount, type 10 characters Scripts: pnpm perf, pnpm perf:baseline, pnpm perf:compare --- packages/benchmarks/.gitignore | 2 + packages/benchmarks/package.json | 6 +- packages/benchmarks/scripts/perf-compare.mjs | 16 + .../__perf__/base-ui/Accordion.perf-test.tsx | 49 +++ .../src/__perf__/base-ui/Button.perf-test.tsx | 22 ++ .../__perf__/base-ui/Checkbox.perf-test.tsx | 49 +++ .../src/__perf__/base-ui/Dialog.perf-test.tsx | 59 +++ .../src/__perf__/base-ui/Input.perf-test.tsx | 46 +++ .../src/__perf__/base-ui/Select.perf-test.tsx | 66 ++++ .../src/__perf__/base-ui/Tabs.perf-test.tsx | 54 +++ .../src/__perf__/utils/theme-wrapper.tsx | 10 + packages/benchmarks/src/setup-perf.ts | 17 + packages/benchmarks/src/setup.ts | 25 ++ packages/benchmarks/vitest.config.perf.ts | 58 +++ pnpm-lock.yaml | 347 ++++++++++++++++++ 15 files changed, 825 insertions(+), 1 deletion(-) create mode 100644 packages/benchmarks/scripts/perf-compare.mjs create mode 100644 packages/benchmarks/src/__perf__/base-ui/Accordion.perf-test.tsx create mode 100644 packages/benchmarks/src/__perf__/base-ui/Button.perf-test.tsx create mode 100644 packages/benchmarks/src/__perf__/base-ui/Checkbox.perf-test.tsx create mode 100644 packages/benchmarks/src/__perf__/base-ui/Dialog.perf-test.tsx create mode 100644 packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx create mode 100644 packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx create mode 100644 packages/benchmarks/src/__perf__/base-ui/Tabs.perf-test.tsx create mode 100644 packages/benchmarks/src/__perf__/utils/theme-wrapper.tsx create mode 100644 packages/benchmarks/src/setup-perf.ts create mode 100644 packages/benchmarks/vitest.config.perf.ts diff --git a/packages/benchmarks/.gitignore b/packages/benchmarks/.gitignore index 24cefbf..d61b80a 100644 --- a/packages/benchmarks/.gitignore +++ b/packages/benchmarks/.gitignore @@ -2,3 +2,5 @@ traces/ # Benchmark JSON results and reports (generated by bench:json / bench:report) results/ +# Reassure performance test output (generated by perf / perf:baseline) +.reassure/ diff --git a/packages/benchmarks/package.json b/packages/benchmarks/package.json index 7ec9791..e6616e6 100644 --- a/packages/benchmarks/package.json +++ b/packages/benchmarks/package.json @@ -6,7 +6,10 @@ "type": "module", "scripts": { "bench": "vitest bench --run", - "bench:browser": "vitest bench --config vitest.config.browser.ts --run" + "bench:browser": "vitest bench --config vitest.config.browser.ts --run", + "perf": "rm -f .reassure/current.perf && vitest --run --config vitest.config.perf.ts", + "perf:baseline": "rm -f .reassure/baseline.perf && REASSURE_OUTPUT_FILE=.reassure/baseline.perf vitest --run --config vitest.config.perf.ts", + "perf:compare": "node scripts/perf-compare.mjs" }, "devDependencies": { "@codspeed/vitest-plugin": "^5.2.0", @@ -22,6 +25,7 @@ "jsdom": "^25.0.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "reassure": "^1.4.1", "typescript": "^5.9.2", "vite": "^7.3.1", "vitest": "^4.0.0" diff --git a/packages/benchmarks/scripts/perf-compare.mjs b/packages/benchmarks/scripts/perf-compare.mjs new file mode 100644 index 0000000..204dbf9 --- /dev/null +++ b/packages/benchmarks/scripts/perf-compare.mjs @@ -0,0 +1,16 @@ +/** + * Compare Reassure performance results between baseline and current runs. + * + * Usage: + * pnpm perf:baseline # run on base branch, writes .reassure/baseline.perf + * pnpm perf # run on current branch, writes .reassure/current.perf + * pnpm perf:compare # compare and output report + */ +import { compare } from '@callstack/reassure-compare'; + +await compare({ + baselineFile: '.reassure/baseline.perf', + currentFile: '.reassure/current.perf', + outputFile: '.reassure/output.json', + outputFormat: 'all', +}); diff --git a/packages/benchmarks/src/__perf__/base-ui/Accordion.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Accordion.perf-test.tsx new file mode 100644 index 0000000..3aa335b --- /dev/null +++ b/packages/benchmarks/src/__perf__/base-ui/Accordion.perf-test.tsx @@ -0,0 +1,49 @@ +import { test } from 'vitest'; +import { measureRenders } from 'reassure'; +import { fireEvent, screen } from '@testing-library/react'; +import { Accordion } from '@omniview/base-ui'; +import { ThemeWrapper } from '../utils/theme-wrapper'; + +function FiveItemAccordion() { + return ( + + {Array.from({ length: 5 }, (_, i) => ( + + Content for section {i} + + ))} + + ); +} + +test('Accordion: mount 5 items', async () => { + await measureRenders(, { wrapper: ThemeWrapper }); +}); + +/** + * Expanding one section should NOT re-render sibling sections. + * This catches shared-context over-notification patterns. + */ +test('Accordion: expand one section', async () => { + await measureRenders(, { + wrapper: ThemeWrapper, + scenario: async () => { + fireEvent.click(screen.getByText('Section 2')); + }, + }); +}); + +/** + * Expanding then collapsing — tests the full toggle cycle. + * Render count should be similar to a single expand. + */ +test('Accordion: expand then collapse', async () => { + await measureRenders(, { + wrapper: ThemeWrapper, + scenario: async () => { + const trigger = screen.getByText('Section 2'); + fireEvent.click(trigger); + fireEvent.click(trigger); + }, + }); +}); diff --git a/packages/benchmarks/src/__perf__/base-ui/Button.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Button.perf-test.tsx new file mode 100644 index 0000000..a33554c --- /dev/null +++ b/packages/benchmarks/src/__perf__/base-ui/Button.perf-test.tsx @@ -0,0 +1,22 @@ +import { test } from 'vitest'; +import { measureRenders } from 'reassure'; +import { Button } from '@omniview/base-ui'; +import { ThemeWrapper } from '../utils/theme-wrapper'; + +/** + * Button is the simplest interactive component — its render count + * serves as the baseline for all other components. + * Expected: 1 render on mount, 0 issues. + */ +test('Button: mount', async () => { + await measureRenders(, { wrapper: ThemeWrapper }); +}); + +test('Button: mount with decorators', async () => { + await measureRenders( + , + { wrapper: ThemeWrapper }, + ); +}); diff --git a/packages/benchmarks/src/__perf__/base-ui/Checkbox.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Checkbox.perf-test.tsx new file mode 100644 index 0000000..5f3c972 --- /dev/null +++ b/packages/benchmarks/src/__perf__/base-ui/Checkbox.perf-test.tsx @@ -0,0 +1,49 @@ +import { test } from 'vitest'; +import { measureRenders } from 'reassure'; +import { fireEvent, screen } from '@testing-library/react'; +import { Checkbox } from '@omniview/base-ui'; +import { ThemeWrapper } from '../utils/theme-wrapper'; + +test('Checkbox: mount', async () => { + await measureRenders( + Accept terms, + { wrapper: ThemeWrapper }, + ); +}); + +test('Checkbox: toggle checked', async () => { + await measureRenders( + Accept terms, + { + wrapper: ThemeWrapper, + scenario: async () => { + const checkbox = screen.getByRole('checkbox'); + fireEvent.click(checkbox); + }, + }, + ); +}); + +/** + * Critical scenario: toggling one checkbox in a list. + * If memoization is missing, ALL checkboxes re-render on a single toggle. + */ +test('Checkbox: toggle one in list of 20', async () => { + const items = Array.from({ length: 20 }, (_, i) => `Option ${i}`); + await measureRenders( +
+ {items.map((label) => ( + + {label} + + ))} +
, + { + wrapper: ThemeWrapper, + scenario: async () => { + const checkboxes = screen.getAllByRole('checkbox'); + fireEvent.click(checkboxes[0]!); + }, + }, + ); +}); diff --git a/packages/benchmarks/src/__perf__/base-ui/Dialog.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Dialog.perf-test.tsx new file mode 100644 index 0000000..30533e6 --- /dev/null +++ b/packages/benchmarks/src/__perf__/base-ui/Dialog.perf-test.tsx @@ -0,0 +1,59 @@ +import { useState } from 'react'; +import { test } from 'vitest'; +import { measureRenders } from 'reassure'; +import { fireEvent, screen } from '@testing-library/react'; +import { Dialog, Button } from '@omniview/base-ui'; +import { ThemeWrapper } from '../utils/theme-wrapper'; + +function DialogWithTrigger() { + const [open, setOpen] = useState(false); + return ( + <> + + setOpen(false)} size="md"> + Confirm Action + Are you sure you want to proceed? + + + + + + + + ); +} + +/** + * Mount cost includes the trigger but NOT the dialog body (open=false). + * Render count should be minimal — just the trigger + null dialog. + */ +test('Dialog: mount (closed)', async () => { + await measureRenders(, { wrapper: ThemeWrapper }); +}); + +/** + * Opening the dialog triggers portal mount, backdrop, and content render. + * High render counts here signal excessive setState chains in useEffect. + */ +test('Dialog: open', async () => { + await measureRenders(, { + wrapper: ThemeWrapper, + scenario: async () => { + fireEvent.click(screen.getByText('Open Dialog')); + }, + }); +}); + +/** + * Full open/close cycle. Render count should be ~2x the open-only count. + * Significantly more signals cleanup-related re-renders. + */ +test('Dialog: open then close', async () => { + await measureRenders(, { + wrapper: ThemeWrapper, + scenario: async () => { + fireEvent.click(screen.getByText('Open Dialog')); + fireEvent.click(screen.getByText('Cancel')); + }, + }); +}); diff --git a/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx new file mode 100644 index 0000000..ae8fc74 --- /dev/null +++ b/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx @@ -0,0 +1,46 @@ +import { test } from 'vitest'; +import { measureRenders } from 'reassure'; +import { fireEvent, screen } from '@testing-library/react'; +import { Input, TextField } from '@omniview/base-ui'; +import { ThemeWrapper } from '../utils/theme-wrapper'; + +test('Input: mount', async () => { + await measureRenders( + + Username + + , + { wrapper: ThemeWrapper }, + ); +}); + +test('TextField: mount', async () => { + await measureRenders( + + Email + + , + { wrapper: ThemeWrapper }, + ); +}); + +/** + * Typing in an input field — each keystroke should cause minimal re-renders. + * High counts here signal uncontrolled→controlled mismatches or expensive + * parent re-renders from onChange. + */ +test('Input: type 10 characters', async () => { + await measureRenders( + + Username + + , + { + wrapper: ThemeWrapper, + scenario: async () => { + const input = screen.getByTestId('input'); + fireEvent.change(input, { target: { value: 'helloworld' } }); + }, + }, + ); +}); diff --git a/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx new file mode 100644 index 0000000..1e464bb --- /dev/null +++ b/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx @@ -0,0 +1,66 @@ +import { test } from 'vitest'; +import { measureRenders } from 'reassure'; +import { fireEvent, screen } from '@testing-library/react'; +import { Select } from '@omniview/base-ui'; +import { makeOptions } from '../../utils/factories'; +import { ThemeWrapper } from '../utils/theme-wrapper'; + +const options = makeOptions(20); + +test('Select: mount with 20 options', async () => { + await measureRenders( + , + { wrapper: ThemeWrapper }, + ); +}); + +/** + * Opening a Select triggers portal mount, positioner layout, and popup render. + * High render counts here signal excessive context notifications. + */ +test('Select: open dropdown', async () => { + await measureRenders( + , + { + wrapper: ThemeWrapper, + scenario: async () => { + const trigger = screen.getByRole('combobox'); + fireEvent.click(trigger); + }, + }, + ); +}); diff --git a/packages/benchmarks/src/__perf__/base-ui/Tabs.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Tabs.perf-test.tsx new file mode 100644 index 0000000..cbc8725 --- /dev/null +++ b/packages/benchmarks/src/__perf__/base-ui/Tabs.perf-test.tsx @@ -0,0 +1,54 @@ +import { test } from 'vitest'; +import { measureRenders } from 'reassure'; +import { fireEvent, screen } from '@testing-library/react'; +import { Tabs } from '@omniview/base-ui'; +import { ThemeWrapper } from '../utils/theme-wrapper'; + +test('Tabs: mount with 5 tabs', async () => { + await measureRenders( + + + {Array.from({ length: 5 }, (_, i) => ( + + Tab {i} + + ))} + + {Array.from({ length: 5 }, (_, i) => ( + + Content for tab {i} + + ))} + , + { wrapper: ThemeWrapper }, + ); +}); + +/** + * Switching tabs should ideally only re-render the old and new panels, + * not the entire tree. High render counts here signal over-rendering. + */ +test('Tabs: switch active tab', async () => { + await measureRenders( + + + {Array.from({ length: 5 }, (_, i) => ( + + Tab {i} + + ))} + + {Array.from({ length: 5 }, (_, i) => ( + + Content for tab {i} + + ))} + , + { + wrapper: ThemeWrapper, + scenario: async () => { + fireEvent.click(screen.getByText('Tab 3')); + }, + }, + ); +}); diff --git a/packages/benchmarks/src/__perf__/utils/theme-wrapper.tsx b/packages/benchmarks/src/__perf__/utils/theme-wrapper.tsx new file mode 100644 index 0000000..fc9e35b --- /dev/null +++ b/packages/benchmarks/src/__perf__/utils/theme-wrapper.tsx @@ -0,0 +1,10 @@ +import type { ReactElement } from 'react'; +import { ThemeProvider } from '@omniview/base-ui'; + +/** + * Wrapper for Reassure tests — provides the ThemeProvider context + * required by all @omniview components. + */ +export function ThemeWrapper({ children }: { children: ReactElement }) { + return {children}; +} diff --git a/packages/benchmarks/src/setup-perf.ts b/packages/benchmarks/src/setup-perf.ts new file mode 100644 index 0000000..0853ead --- /dev/null +++ b/packages/benchmarks/src/setup-perf.ts @@ -0,0 +1,17 @@ +import { configure } from 'reassure'; +import fs from 'node:fs'; +import path from 'node:path'; + +// Ensure .reassure directory exists +const reassureDir = path.resolve(__dirname, '../.reassure'); +if (!fs.existsSync(reassureDir)) { + fs.mkdirSync(reassureDir, { recursive: true }); +} + +// Tell Reassure to use @testing-library/react explicitly +configure({ + testingLibrary: 'react', + runs: 20, + warmupRuns: 3, +}); + diff --git a/packages/benchmarks/src/setup.ts b/packages/benchmarks/src/setup.ts index 5f7aac7..cdca5e9 100644 --- a/packages/benchmarks/src/setup.ts +++ b/packages/benchmarks/src/setup.ts @@ -24,6 +24,31 @@ if (typeof window !== 'undefined' && typeof window.matchMedia !== 'function') { }) as MediaQueryList; } +// @base-ui/react uses PointerEvent in click handlers (checkbox, button, etc.) +if (typeof globalThis.PointerEvent === 'undefined') { + globalThis.PointerEvent = class PointerEvent extends MouseEvent { + readonly pointerId: number; + readonly width: number; + readonly height: number; + readonly pressure: number; + readonly tiltX: number; + readonly tiltY: number; + readonly pointerType: string; + readonly isPrimary: boolean; + constructor(type: string, params: PointerEventInit = {}) { + super(type, params); + this.pointerId = params.pointerId ?? 0; + this.width = params.width ?? 1; + this.height = params.height ?? 1; + this.pressure = params.pressure ?? 0; + this.tiltX = params.tiltX ?? 0; + this.tiltY = params.tiltY ?? 0; + this.pointerType = params.pointerType ?? ''; + this.isPrimary = params.isPrimary ?? false; + } + } as unknown as typeof PointerEvent; +} + if (typeof globalThis.IntersectionObserver === 'undefined') { globalThis.IntersectionObserver = class IntersectionObserver { readonly root = null; diff --git a/packages/benchmarks/vitest.config.perf.ts b/packages/benchmarks/vitest.config.perf.ts new file mode 100644 index 0000000..e43909b --- /dev/null +++ b/packages/benchmarks/vitest.config.perf.ts @@ -0,0 +1,58 @@ +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; +import path from 'node:path'; + +/** + * Vitest config for Reassure performance tests. + * + * These are regular test() calls (not bench()) that use Reassure's measureRenders + * to track render counts and durations per interaction scenario. The output goes + * to .reassure/current.perf (or baseline.perf) for cross-branch comparison. + * + * V8 flags match Reassure's recommendations: disable JIT for stable measurements. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export default defineConfig(async () => { + return { + plugins: [ + react({ + babel: { + plugins: [['babel-plugin-react-compiler', {}]], + }, + }), + ], + resolve: { + alias: [ + { find: '@omniview/base-ui', replacement: path.resolve(__dirname, '../base-ui/src/index.ts') }, + { find: '@omniview/editors', replacement: path.resolve(__dirname, '../editors/src/index.ts') }, + { + find: /^react-syntax-highlighter(\/.*)?$/, + replacement: path.resolve(__dirname, 'src/stubs/react-syntax-highlighter.ts'), + }, + ], + }, + test: { + include: ['src/**/*.perf-test.{ts,tsx}'], + environment: 'jsdom', + // Reassure's writeTestStats uses expect.getState().currentTestName + globals: true, + setupFiles: ['./src/setup.ts', './src/setup-perf.ts'], + pool: 'forks', + fileParallelism: false, + // Reassure recommends --no-turbofan --no-sparkplug for stable measurements. + // --expose-gc lets Reassure force GC between runs to reduce noise. + forks: { + execArgv: [ + '--expose-gc', + '--no-turbofan', + '--no-sparkplug', + '--hash-seed=1', + '--random-seed=1', + '--max-old-space-size=4096', + ], + }, + // Reassure tests are slow (20 runs + warmup per test). Generous timeout. + testTimeout: 60_000, + }, + }; +}) as ReturnType; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55a968b..5cc1e78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -247,6 +247,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.2.4(react@19.2.4) + reassure: + specifier: ^1.4.1 + version: 1.4.1(react@19.2.4) typescript: specifier: ^5.9.2 version: 5.9.3 @@ -473,6 +476,24 @@ packages: '@types/react': optional: true + '@callstack/reassure-cli@1.4.1': + resolution: {integrity: sha512-NKJi/WoLiDkn3FnYJhOk+FhWsdvrytrAfHRqexrCeyVfwpgbM+laPWto2+EKbfKdrHWBqWgbBOWyFoMiOAXcVg==} + hasBin: true + + '@callstack/reassure-compare@1.4.1': + resolution: {integrity: sha512-qJgASbKlBWA37XSN5b/uVAvc524dd9s3grumCKabI2GHkA5w5G6WWI11DnJPzjBnoZ/oIgNSrOm2sm6UMEvsVQ==} + + '@callstack/reassure-danger@1.4.1': + resolution: {integrity: sha512-TTfliolOt8Tfq4yu8vdj4lRdki5Vuvwxj22K2+xpAjxiF2rT4BL6gwM3YIPVUwuQMN3kQ8d8TuGJ4Wlc3zpAUA==} + + '@callstack/reassure-logger@1.4.1': + resolution: {integrity: sha512-2uB6OBk0/IdSXUpdTsMcN40d3ly5xWWPRjs0G4zEr37tyHB0lmJVuQIR5elUjIs+fTuK48PVuqW/4+Yce7WlFA==} + + '@callstack/reassure-measure@1.4.1': + resolution: {integrity: sha512-EPUKuMgzz0bccf/nn+6A5PhunZS/K3h0rpvhdOsPzGl3abjROoPi7bQN/ipURsLwle+OonqLCigcOK/CnndLEg==} + peerDependencies: + react: '>=18.0.0' + '@codspeed/core@5.2.0': resolution: {integrity: sha512-CmDhpWjcOJg2iBOQ/BmBnSBq8qxlM3r4h8uvYDkoUaba+EKRT3T73BZtKuml/48jZMsB+4/FG2UbTBinDWtuvw==} @@ -764,6 +785,10 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@jest/schemas@30.0.5': + resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4': resolution: {integrity: sha512-6PyZBYKnnVNqOSB0YFly+62R7dmov8segT27A+RVTBVd4iAE6kbW9QBJGlyR2yG4D4ohzhZSTIu7BK1UTtmFFA==} peerDependencies: @@ -789,6 +814,12 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + + '@kwsites/promise-deferred@1.1.1': + resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@mdx-js/react@3.1.1': resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} peerDependencies: @@ -975,6 +1006,9 @@ packages: '@rushstack/ts-command-line@5.3.3': resolution: {integrity: sha512-c+ltdcvC7ym+10lhwR/vWiOhsrm/bP3By2VsFcs5qTKv+6tTmxgbVrtJ5NdNjANiV5TcmOZgUN+5KYQ4llsvEw==} + '@sinclair/typebox@0.34.48': + resolution: {integrity: sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1537,6 +1571,10 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1554,6 +1592,9 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + complex.js@2.4.3: + resolution: {integrity: sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1677,6 +1718,9 @@ packages: electron-to-chromium@1.5.307: resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + empathic@2.0.0: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} @@ -1733,6 +1777,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-latex@1.2.0: + resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1857,6 +1904,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1893,6 +1944,9 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + fs-extra@11.3.4: resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} engines: {node: '>=14.14'} @@ -1920,6 +1974,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2058,6 +2116,11 @@ packages: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -2127,6 +2190,10 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-generator-function@1.1.2: resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} @@ -2212,6 +2279,9 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + javascript-natural-sort@0.7.1: + resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} + jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} @@ -2277,6 +2347,10 @@ packages: resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -2332,6 +2406,11 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mathjs@15.1.1: + resolution: {integrity: sha512-rM668DTtpSzMVoh/cKAllyQVEbBApM5g//IMGD8vD7YlrIz9ITRr3SrdhjaDxcBNTdyETWwPebj2unZyHD7ZdA==} + engines: {node: '>= 18'} + hasBin: true + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -2591,6 +2670,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -2599,6 +2682,10 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -2607,6 +2694,10 @@ packages: resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2653,6 +2744,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -2680,6 +2775,10 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@30.3.0: + resolution: {integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -2725,6 +2824,9 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-markdown@9.1.0: resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==} peerDependencies: @@ -2745,6 +2847,10 @@ packages: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} + reassure@1.4.1: + resolution: {integrity: sha512-EeW23/ci4mGHsv4WDSK9jTzzi5yCu3Gdf3GgugKvi5NrqxCiVubiTlcz8bjfpipSFEUK+mpDtlhyJI/6Kmkgjg==} + hasBin: true + recast@0.23.11: resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} engines: {node: '>= 4'} @@ -2782,6 +2888,10 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -2789,10 +2899,18 @@ packages: reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} @@ -2840,6 +2958,9 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + seedrandom@3.0.5: + resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2893,6 +3014,9 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + simple-git@3.33.0: + resolution: {integrity: sha512-D4V/tGC2sjsoNhoMybKyGoE+v8A60hRawKQ1iFRA1zwuDgGZCBJ4ByOzZ5J8joBbi4Oam0qiPH+GhzmSBwbJng==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2930,6 +3054,10 @@ packages: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + string.prototype.matchall@4.0.12: resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} engines: {node: '>= 0.4'} @@ -2952,6 +3080,10 @@ packages: stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -2992,6 +3124,9 @@ packages: tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + tiny-emitter@2.1.0: + resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -3049,6 +3184,14 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} + ts-markdown-builder@0.5.0: + resolution: {integrity: sha512-/2pzAFjGwk5fUR8ftFMhCOm/zmoqtpYWK6209j3YPfgWF2I+eYY6PKpR0mBJte6s8McnkQ/T92fV+IJg6bdxyw==} + engines: {node: '>= 18.0.0'} + + ts-regex-builder@1.8.2: + resolution: {integrity: sha512-Y8HovHFheDKm/jgLIWSO8o81xA/I9O5AGc3/vNG1sVSskatOifr3SQzAsatBXGLjL3nYhQif1MpwQRS5GF8ADg==} + engines: {node: '>= 18.0.0'} + tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -3076,6 +3219,10 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typed-function@4.2.2: + resolution: {integrity: sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A==} + engines: {node: '>= 18'} + typescript-eslint@8.57.0: resolution: {integrity: sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3308,6 +3455,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + ws@8.19.0: resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} @@ -3331,6 +3482,10 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3342,6 +3497,14 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -3350,6 +3513,9 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -3503,6 +3669,36 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@callstack/reassure-cli@1.4.1': + dependencies: + '@callstack/reassure-compare': 1.4.1 + '@callstack/reassure-logger': 1.4.1 + chalk: 4.1.2 + simple-git: 3.33.0 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + + '@callstack/reassure-compare@1.4.1': + dependencies: + '@callstack/reassure-logger': 1.4.1 + ts-markdown-builder: 0.5.0 + ts-regex-builder: 1.8.2 + zod: 4.3.6 + + '@callstack/reassure-danger@1.4.1': {} + + '@callstack/reassure-logger@1.4.1': + dependencies: + chalk: 4.1.2 + + '@callstack/reassure-measure@1.4.1(react@19.2.4)': + dependencies: + '@callstack/reassure-logger': 1.4.1 + mathjs: 15.1.1 + pretty-format: 30.3.0 + react: 19.2.4 + '@codspeed/core@5.2.0': dependencies: axios: 1.13.6 @@ -3725,6 +3921,10 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@jest/schemas@30.0.5': + dependencies: + '@sinclair/typebox': 0.34.48 + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.15)(yaml@2.8.2))': dependencies: glob: 13.0.6 @@ -3752,6 +3952,14 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@kwsites/promise-deferred@1.1.1': {} + '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: '@types/mdx': 2.0.13 @@ -3918,6 +4126,8 @@ snapshots: transitivePeerDependencies: - '@types/node' + '@sinclair/typebox@0.34.48': {} + '@standard-schema/spec@1.1.0': {} '@storybook/addon-a11y@10.2.17(storybook@10.2.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': @@ -4586,6 +4796,12 @@ snapshots: check-error@2.1.3: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -4600,6 +4816,8 @@ snapshots: compare-versions@6.1.1: {} + complex.js@2.4.3: {} + concat-map@0.0.1: {} confbox@0.1.8: {} @@ -4713,6 +4931,8 @@ snapshots: electron-to-chromium@1.5.307: {} + emoji-regex@8.0.0: {} + empathic@2.0.0: {} entities@6.0.1: {} @@ -4854,6 +5074,8 @@ snapshots: escalade@3.2.0: {} + escape-latex@1.2.0: {} + escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -4992,6 +5214,11 @@ snapshots: dependencies: flat-cache: 4.0.1 + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -5025,6 +5252,8 @@ snapshots: format@0.2.2: {} + fraction.js@5.3.4: {} + fs-extra@11.3.4: dependencies: graceful-fs: 4.2.11 @@ -5051,6 +5280,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5242,6 +5473,11 @@ snapshots: import-lazy@4.0.0: {} + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -5311,6 +5547,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.2: dependencies: call-bound: 1.0.4 @@ -5398,6 +5636,8 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + javascript-natural-sort@0.7.1: {} + jju@1.4.0: {} js-tokens@4.0.0: {} @@ -5478,6 +5718,10 @@ snapshots: pkg-types: 2.3.0 quansync: 0.2.11 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -5525,6 +5769,18 @@ snapshots: math-intrinsics@1.1.0: {} + mathjs@15.1.1: + dependencies: + '@babel/runtime': 7.28.6 + complex.js: 2.4.3 + decimal.js: 10.6.0 + escape-latex: 1.2.0 + fraction.js: 5.3.4 + javascript-natural-sort: 0.7.1 + seedrandom: 3.0.5 + tiny-emitter: 2.1.0 + typed-function: 4.2.2 + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -6018,6 +6274,10 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -6026,6 +6286,10 @@ snapshots: dependencies: yocto-queue: 1.2.2 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -6034,6 +6298,8 @@ snapshots: dependencies: p-limit: 4.0.0 + p-try@2.2.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -6075,6 +6341,10 @@ snapshots: picomatch@4.0.3: {} + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -6105,6 +6375,12 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + pretty-format@30.3.0: + dependencies: + '@jest/schemas': 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 + prismjs@1.30.0: {} prop-types@15.8.1: @@ -6153,6 +6429,8 @@ snapshots: react-is@17.0.2: {} + react-is@18.3.1: {} + react-markdown@9.1.0(@types/react@19.2.14)(react@19.2.4): dependencies: '@types/hast': 3.0.4 @@ -6185,6 +6463,17 @@ snapshots: react@19.2.4: {} + reassure@1.4.1(react@19.2.4): + dependencies: + '@callstack/reassure-cli': 1.4.1 + '@callstack/reassure-compare': 1.4.1 + '@callstack/reassure-danger': 1.4.1 + '@callstack/reassure-measure': 1.4.1(react@19.2.4) + import-local: 3.2.0 + transitivePeerDependencies: + - react + - supports-color + recast@0.23.11: dependencies: ast-types: 0.16.1 @@ -6270,12 +6559,20 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + require-directory@2.1.1: {} + require-from-string@2.0.2: {} reselect@5.1.1: {} + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} + resolve@1.22.11: dependencies: is-core-module: 2.16.1 @@ -6355,6 +6652,8 @@ snapshots: scheduler@0.27.0: {} + seedrandom@3.0.5: {} + semver@6.3.1: {} semver@7.5.4: @@ -6421,6 +6720,14 @@ snapshots: siginfo@2.0.0: {} + simple-git@3.33.0: + dependencies: + '@kwsites/file-exists': 1.1.1 + '@kwsites/promise-deferred': 1.1.1 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + source-map-js@1.2.1: {} source-map@0.6.1: {} @@ -6463,6 +6770,12 @@ snapshots: string-argv@0.3.2: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 @@ -6512,6 +6825,10 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-bom@3.0.0: {} strip-indent@3.0.0: @@ -6544,6 +6861,8 @@ snapshots: tabbable@6.4.0: {} + tiny-emitter@2.1.0: {} + tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -6585,6 +6904,10 @@ snapshots: ts-dedent@2.2.0: {} + ts-markdown-builder@0.5.0: {} + + ts-regex-builder@1.8.2: {} + tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 @@ -6630,6 +6953,8 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typed-function@4.2.2: {} + typescript-eslint@8.57.0(eslint@9.39.4)(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) @@ -6883,6 +7208,12 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + ws@8.19.0: {} wsl-utils@0.1.0: @@ -6893,14 +7224,30 @@ snapshots: xmlchars@2.2.0: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yallist@4.0.0: {} yaml@2.8.2: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} + zod@4.3.6: {} + zwitch@2.0.4: {} From 481aba4e8a1cc80d9da706bece78d9ec29735d89 Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Thu, 12 Mar 2026 15:16:53 -0500 Subject: [PATCH 13/13] fix(benchmarks): address PR review feedback on Reassure perf tests - Add @callstack/reassure-compare to devDependencies (pnpm strict mode) - Rewrite Input typing test with controlled component and per-keystroke changes - Await listbox render after Select dropdown open - Replace __dirname with import.meta.url derivation in ESM files - Document async factory + cast rationale in vitest.config.perf.ts --- packages/benchmarks/package.json | 1 + .../src/__perf__/base-ui/Input.perf-test.tsx | 41 +++++++++++++------ .../src/__perf__/base-ui/Select.perf-test.tsx | 1 + packages/benchmarks/src/setup-perf.ts | 3 ++ packages/benchmarks/vitest.config.perf.ts | 8 +++- pnpm-lock.yaml | 3 ++ 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/benchmarks/package.json b/packages/benchmarks/package.json index e6616e6..ef4910b 100644 --- a/packages/benchmarks/package.json +++ b/packages/benchmarks/package.json @@ -12,6 +12,7 @@ "perf:compare": "node scripts/perf-compare.mjs" }, "devDependencies": { + "@callstack/reassure-compare": "^1.4.1", "@codspeed/vitest-plugin": "^5.2.0", "@omniview/base-ui": "workspace:*", "@omniview/editors": "workspace:*", diff --git a/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx index ae8fc74..5867d66 100644 --- a/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx +++ b/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx @@ -1,4 +1,5 @@ import { test } from 'vitest'; +import { type ChangeEvent, useState } from 'react'; import { measureRenders } from 'reassure'; import { fireEvent, screen } from '@testing-library/react'; import { Input, TextField } from '@omniview/base-ui'; @@ -24,23 +25,39 @@ test('TextField: mount', async () => { ); }); +/** + * Controlled input wrapper so each fireEvent.change triggers a state update + * and re-render, simulating real per-keystroke behavior. + */ +function ControlledInput() { + const [value, setValue] = useState(''); + return ( + + Username + ) => setValue(e.target.value)} + /> + + ); +} + /** * Typing in an input field — each keystroke should cause minimal re-renders. * High counts here signal uncontrolled→controlled mismatches or expensive * parent re-renders from onChange. */ test('Input: type 10 characters', async () => { - await measureRenders( - - Username - - , - { - wrapper: ThemeWrapper, - scenario: async () => { - const input = screen.getByTestId('input'); - fireEvent.change(input, { target: { value: 'helloworld' } }); - }, + await measureRenders(, { + wrapper: ThemeWrapper, + scenario: async () => { + const input = screen.getByTestId('input'); + const chars = 'helloworld'; + for (let i = 0; i < chars.length; i++) { + fireEvent.change(input, { target: { value: chars.slice(0, i + 1) } }); + } }, - ); + }); }); diff --git a/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx index 1e464bb..ad1471d 100644 --- a/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx +++ b/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx @@ -60,6 +60,7 @@ test('Select: open dropdown', async () => { scenario: async () => { const trigger = screen.getByRole('combobox'); fireEvent.click(trigger); + await screen.findByRole('listbox'); }, }, ); diff --git a/packages/benchmarks/src/setup-perf.ts b/packages/benchmarks/src/setup-perf.ts index 0853ead..472da93 100644 --- a/packages/benchmarks/src/setup-perf.ts +++ b/packages/benchmarks/src/setup-perf.ts @@ -1,8 +1,11 @@ import { configure } from 'reassure'; import fs from 'node:fs'; import path from 'node:path'; +import { fileURLToPath } from 'node:url'; // Ensure .reassure directory exists +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const reassureDir = path.resolve(__dirname, '../.reassure'); if (!fs.existsSync(reassureDir)) { fs.mkdirSync(reassureDir, { recursive: true }); diff --git a/packages/benchmarks/vitest.config.perf.ts b/packages/benchmarks/vitest.config.perf.ts index e43909b..446294d 100644 --- a/packages/benchmarks/vitest.config.perf.ts +++ b/packages/benchmarks/vitest.config.perf.ts @@ -1,6 +1,10 @@ import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); /** * Vitest config for Reassure performance tests. @@ -10,8 +14,10 @@ import path from 'node:path'; * to .reassure/current.perf (or baseline.perf) for cross-branch comparison. * * V8 flags match Reassure's recommendations: disable JIT for stable measurements. + * + * Note: async factory + cast mirrors vitest.config.ts — needed because Vitest's + * InlineConfig type doesn't include `forks` even though the runtime supports it. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any export default defineConfig(async () => { return { plugins: [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cc1e78..2ccac93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,6 +208,9 @@ importers: packages/benchmarks: devDependencies: + '@callstack/reassure-compare': + specifier: ^1.4.1 + version: 1.4.1 '@codspeed/vitest-plugin': specifier: ^5.2.0 version: 5.2.0(tinybench@2.9.0)(vite@7.3.1(@types/node@22.19.15)(yaml@2.8.2))(vitest@4.0.18(@types/node@22.19.15)(jsdom@25.0.1)(yaml@2.8.2))