mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-11 08:43:48 +00:00
🧪 ci: Resolve DataTable test infinite re-render (#13947)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
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.
This commit is contained in:
parent
5c5ef37e30
commit
03ecac8ac1
4 changed files with 77 additions and 6 deletions
38
.github/workflows/frontend-review.yml
vendored
38
.github/workflows/frontend-review.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }) => (
|
||||
|
|
|
|||
|
|
@ -271,8 +271,10 @@ function DataTable<TData extends Record<string, unknown>, 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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue