From 03ecac8ac1c22c4b6d4b05b83a055fcbeb7da98e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 24 Jun 2026 23:40:18 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20ci:=20Resolve=20DataTable=20test?= =?UTF-8?q?=20infinite=20re-render=20(#13947)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DataTable.spec failed with "Too many re-renders" (35 tests). Root cause: @tanstack/react-virtual is measurement-driven, and jsdom has no real layout, so its re-render loop never converges. This went unnoticed because packages/client had no jest CI job (only the client workspace runs jest in frontend-review.yml). - DataTable: only read the virtualizer (getVirtualItems/getTotalSize) when virtualization is active; the non-virtualized branch renders rows directly, so engaging it for small tables was wasted render-phase work. - Spec: mock @tanstack/react-virtual, since jsdom can't exercise real virtualization layout. - Add a test:ci script to @librechat/client and a Tests: @librechat/client CI job so packages/client specs run on every frontend PR. --- .github/workflows/frontend-review.yml | 38 +++++++++++++++++++ packages/client/package.json | 4 +- .../components/DataTable/DataTable.spec.tsx | 35 +++++++++++++++-- .../src/components/DataTable/DataTable.tsx | 6 ++- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/.github/workflows/frontend-review.yml b/.github/workflows/frontend-review.yml index a3f31efba6..db84d055c5 100644 --- a/.github/workflows/frontend-review.yml +++ b/.github/workflows/frontend-review.yml @@ -122,6 +122,44 @@ jobs: run: npm run typecheck working-directory: client + test-packages-client: + name: 'Tests: @librechat/client' + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js 24.16.0 + uses: actions/setup-node@v4 + with: + node-version: '24.16.0' + + - name: Restore node_modules cache + id: cache-node-modules + uses: actions/cache@v4 + with: + path: | + node_modules + client/node_modules + packages/client/node_modules + packages/data-provider/node_modules + key: node-modules-frontend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} + + - name: Install dependencies + if: steps.cache-node-modules.outputs.cache-hit != 'true' + run: npm ci + + - name: Download data-provider build + uses: actions/download-artifact@v4 + with: + name: build-data-provider + path: packages/data-provider/dist + + - name: Run unit tests + run: npm run test:ci + working-directory: packages/client + test-ubuntu: name: 'Tests: Ubuntu (shard ${{ matrix.shard }}/4)' needs: build diff --git a/packages/client/package.json b/packages/client/package.json index 6c33db4714..7b04926cab 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -33,7 +33,9 @@ "build:dev": "npm run clean && NODE_ENV=development tsdown", "b:build": "bun run b:clean && bun run tsdown", "build:watch": "tsdown --watch", - "dev": "tsdown --watch" + "dev": "tsdown --watch", + "test": "jest", + "test:ci": "jest --ci" }, "peerDependencies": { "@ariakit/react": "^0.4.29", diff --git a/packages/client/src/components/DataTable/DataTable.spec.tsx b/packages/client/src/components/DataTable/DataTable.spec.tsx index f9897acb7e..491ea96823 100644 --- a/packages/client/src/components/DataTable/DataTable.spec.tsx +++ b/packages/client/src/components/DataTable/DataTable.spec.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { Provider as JotaiProvider } from 'jotai'; -import DataTable from './DataTable'; -import type { TableColumn } from './DataTable.types'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import type { SortingState } from '@tanstack/react-table'; +import type { TableColumn } from './DataTable.types'; +import DataTable from './DataTable'; // Mock utilities jest.mock('~/utils', () => ({ @@ -30,6 +30,35 @@ jest.mock('~/hooks', () => ({ useMediaQuery: jest.fn(() => false), })); +// jsdom can't measure layout, so @tanstack/react-virtual's measurement-driven re-render loop +// never converges (infinite "Too many re-renders"). Stub it to render every row deterministically. +jest.mock('@tanstack/react-virtual', () => ({ + useVirtualizer: ({ + count, + estimateSize, + }: { + count: number; + estimateSize?: (index: number) => number; + }) => { + const size = estimateSize ? estimateSize(0) : 56; + return { + getVirtualItems: () => + Array.from({ length: count }, (_, index) => ({ + index, + key: index, + start: index * size, + end: (index + 1) * size, + size, + lane: 0, + })), + getTotalSize: () => count * size, + calculateRange: () => {}, + measureElement: () => {}, + scrollToIndex: () => {}, + }; + }, +})); + // Mock svgs jest.mock('~/svgs', () => ({ Spinner: ({ className }: { className?: string }) => ( diff --git a/packages/client/src/components/DataTable/DataTable.tsx b/packages/client/src/components/DataTable/DataTable.tsx index 2c11155e0a..20bcb7e019 100644 --- a/packages/client/src/components/DataTable/DataTable.tsx +++ b/packages/client/src/components/DataTable/DataTable.tsx @@ -271,8 +271,10 @@ function DataTable, TValue>({ overscan: dynamicOverscan, }); - const virtualRows = rowVirtualizer.getVirtualItems(); - const totalSize = rowVirtualizer.getTotalSize(); + // Only read the virtualizer when active; the non-virtualized branch renders rows directly, + // so engaging it for small tables is wasted render-phase work. + const virtualRows = virtualizationActive ? rowVirtualizer.getVirtualItems() : []; + const totalSize = virtualizationActive ? rowVirtualizer.getTotalSize() : 0; const paddingTop = virtualRows[0]?.start ?? 0; const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows[virtualRows.length - 1]?.end ?? 0) : 0;