Skip to content

Commit 9c1ebf8

Browse files
authored
Merge branch 'main' into vrt
2 parents 60f448c + 58d5e3c commit 9c1ebf8

File tree

12 files changed

+193
-60
lines changed

12 files changed

+193
-60
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- name: Build website
3030
run: node --run build:website
3131
- name: Install Playwright Browsers
32-
run: npx playwright install chromium
32+
run: npx playwright install chromium firefox
3333
- name: Test
3434
run: node --run test
3535
- name: Visual regression test

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
"@typescript-eslint/parser": "^8.39.1",
7070
"@vitejs/plugin-react": "^5.0.0",
7171
"@vitest/browser": "^4.0.0-beta.12",
72-
"@vitest/coverage-v8": "^4.0.0-beta.12",
72+
"@vitest/coverage-istanbul": "^4.0.0-beta.12",
7373
"@vitest/eslint-plugin": "^1.3.4",
7474
"@wyw-in-js/rollup": "^0.7.0",
7575
"@wyw-in-js/vite": "^0.7.0",

src/hooks/useViewportRows.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,48 @@ export function useViewportRows<R>({
2828
};
2929
}
3030

31-
let totalRowHeight = 0;
32-
let gridTemplateRows = ' ';
3331
// Calcule the height of all the rows upfront. This can cause performance issues
3432
// and we can consider using a similar approach as react-window
3533
// https://github.com/bvaughn/react-window/blob/b0a470cc264e9100afcaa1b78ed59d88f7914ad4/src/VariableSizeList.js#L68
36-
const rowPositions = rows.map((row) => {
34+
let totalRowHeight = 0;
35+
let gridTemplateRows = '';
36+
let currentHeight: number | null = null;
37+
let repeatCount = 0;
38+
39+
const rowPositions = rows.map((row, index) => {
3740
const currentRowHeight = rowHeight(row);
38-
const position = { top: totalRowHeight, height: currentRowHeight };
39-
gridTemplateRows += `${currentRowHeight}px `;
41+
42+
const position = {
43+
top: totalRowHeight,
44+
height: currentRowHeight
45+
};
4046
totalRowHeight += currentRowHeight;
47+
48+
if (currentHeight === null) {
49+
currentHeight = currentRowHeight;
50+
repeatCount = 1;
51+
} else if (currentHeight === currentRowHeight) {
52+
// If the current row height is the same as the previous one, increment the repeat count
53+
repeatCount++;
54+
} else {
55+
if (repeatCount > 1) {
56+
gridTemplateRows += `repeat(${repeatCount}, ${currentHeight}px) `;
57+
} else {
58+
gridTemplateRows += `${currentHeight}px `;
59+
}
60+
61+
currentHeight = currentRowHeight;
62+
repeatCount = 1;
63+
}
64+
65+
if (index === rows.length - 1) {
66+
if (repeatCount > 1) {
67+
gridTemplateRows += `repeat(${repeatCount}, ${currentHeight}px)`;
68+
} else {
69+
gridTemplateRows += `${currentHeight}px`;
70+
}
71+
}
72+
4173
return position;
4274
});
4375

test/browser/TextEditor.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ test('TextEditor', async () => {
3838
// input is focused
3939
expect(input).toHaveFocus();
4040
// input value is fully selected
41-
expect(input.selectionStart).toBe(0);
42-
expect(input.selectionEnd).toBe(initialRows[0].name.length);
41+
expect(input).toHaveSelection(initialRows[0].name);
4342

4443
// pressing escape closes the editor without committing
4544
await userEvent.keyboard('Test{escape}');

test/browser/column/grouping.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { page, userEvent } from '@vitest/browser/context';
22

33
import type { ColumnOrColumnGroup } from '../../../src';
4-
import { getSelectedCell, setup, validateCellPosition } from '../utils';
4+
import { getSelectedCell, setup, tabIntoGrid, validateCellPosition } from '../utils';
55

66
const columns: readonly ColumnOrColumnGroup<NonNullable<unknown>>[] = [
77
{ key: 'col1', name: 'col 1' },
@@ -248,12 +248,12 @@ test('grouping', async () => {
248248
});
249249

250250
test('keyboard navigation', async () => {
251-
setup({ columns, rows: [{}] });
251+
setup({ columns, rows: [{}] }, true);
252252

253253
// no initial selection
254254
await expect.element(getSelectedCell()).not.toBeInTheDocument();
255255

256-
await userEvent.tab();
256+
await tabIntoGrid();
257257
validateCellPosition(0, 3);
258258

259259
// arrow navigation

test/browser/column/renderEditCell.test.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ describe('Editor', () => {
4141
it('should open editor when user types', async () => {
4242
page.render(<EditorTest />);
4343
await userEvent.click(getCellsAtRowIndex(0)[0]);
44-
await userEvent.keyboard('123{enter}');
44+
// TODO: await userEvent.keyboard('123{enter}'); fails in FF
45+
await userEvent.keyboard('{enter}123{enter}');
4546
expect(getCellsAtRowIndex(0)[0]).toHaveTextContent(/^1123$/);
4647
});
4748

@@ -99,7 +100,8 @@ describe('Editor', () => {
99100
const editor = page.getByRole('spinbutton', { name: 'col1-editor' });
100101
await expect.element(editor).not.toBeInTheDocument();
101102
expect(getGrid().element().scrollTop).toBe(2000);
102-
await userEvent.keyboard('123');
103+
// TODO: await userEvent.keyboard('123'); fails in FF
104+
await userEvent.keyboard('{enter}123');
103105
expect(getCellsAtRowIndex(0)).toHaveLength(2);
104106
await expect.element(editor).toHaveValue(123);
105107
expect(getGrid().element().scrollTop).toBe(0);
@@ -194,7 +196,8 @@ describe('Editor', () => {
194196
/>
195197
);
196198
await userEvent.click(getCellsAtRowIndex(0)[1]);
197-
await userEvent.keyboard('yz{enter}');
199+
// TODO: await userEvent.keyboard('yz{enter}'); fails in FF
200+
await userEvent.keyboard('{enter}yz{enter}');
198201
expect(getCellsAtRowIndex(0)[1]).toHaveTextContent(/^a1yz$/);
199202
await userEvent.keyboard('x');
200203
await expect

test/browser/column/resizable.test.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,17 @@ test('should auto resize column when resize handle is double clicked', async ()
152152
await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' });
153153
const [, col2] = getHeaderCells();
154154
await autoResize(col2);
155-
await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 327.703px' });
155+
await testGridTemplateColumns('100px 327.703px', '100px 327.833px', '100px 400px');
156156
expect(onColumnResize).toHaveBeenCalledExactlyOnceWith(
157157
expect.objectContaining(columns[1]),
158-
327.703125
158+
// Due to differences in text rendering between browsers the measured width can vary
159+
expect.toSatisfy(
160+
(width) =>
161+
// Chrome and Firefox on windows
162+
(width >= 327.7 && width <= 327.9) ||
163+
// Firefox on CI
164+
width === 400
165+
)
159166
);
160167
});
161168

@@ -229,15 +236,23 @@ test('should remeasure flex columns when resizing a column', async () => {
229236
],
230237
onColumnResize
231238
});
232-
const grid = getGrid();
233-
await expect.element(grid).toHaveStyle({ gridTemplateColumns: '639.328px 639.328px 639.344px' });
239+
240+
await testGridTemplateColumns('639.328px 639.328px 639.344px', '639.333px 639.333px 639.333px');
234241
const [col1] = getHeaderCells();
235242
await autoResize(col1);
236-
await expect.element(grid).toHaveStyle({ gridTemplateColumns: '79.1406px 919.422px 919.438px' });
243+
await testGridTemplateColumns(
244+
'79.1406px 919.422px 919.438px',
245+
'79.1667px 919.417px 919.417px',
246+
'100.5px 908.75px 908.75px'
247+
);
237248
expect(onColumnResize).toHaveBeenCalledOnce();
238249
// onColumnResize is not called if width is not changed
239250
await autoResize(col1);
240-
await expect.element(grid).toHaveStyle({ gridTemplateColumns: '79.1406px 919.422px 919.438px' });
251+
await testGridTemplateColumns(
252+
'79.1406px 919.422px 919.438px',
253+
'79.1667px 919.417px 919.417px',
254+
'100.5px 908.75px 908.75px'
255+
);
241256
expect(onColumnResize).toHaveBeenCalledOnce();
242257
});
243258

@@ -330,3 +345,15 @@ test('should use columnWidths and onColumnWidthsChange props when provided', asy
330345
// ])
331346
// );
332347
});
348+
349+
async function testGridTemplateColumns(chrome: string, firefox: string, firefoxCI = firefox) {
350+
const grid = getGrid();
351+
if (navigator.userAgent.includes('Chrome')) {
352+
await expect.element(grid).toHaveStyle({ gridTemplateColumns: chrome });
353+
} else {
354+
expect((grid.element() as HTMLDivElement).style.gridTemplateColumns).toBeOneOf([
355+
firefox,
356+
firefoxCI
357+
]);
358+
}
359+
}

test/browser/direction.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { userEvent } from '@vitest/browser/context';
22

33
import type { Column } from '../../src';
4-
import { getGrid, getSelectedCell, setup } from './utils';
4+
import { getGrid, getSelectedCell, setup, tabIntoGrid } from './utils';
55

66
interface Row {
77
id: number;
@@ -22,27 +22,27 @@ const columns: readonly Column<Row>[] = [
2222
const rows: readonly Row[] = [];
2323

2424
test('should use left to right direction by default', async () => {
25-
setup({ rows, columns });
25+
setup({ rows, columns }, true);
2626
await expect.element(getGrid()).toHaveAttribute('dir', 'ltr');
27-
await userEvent.tab();
27+
await tabIntoGrid();
2828
await expect.element(getSelectedCell()).toHaveTextContent('ID');
2929
await userEvent.keyboard('{ArrowRight}');
3030
await expect.element(getSelectedCell()).toHaveTextContent('Name');
3131
});
3232

3333
test('should use left to right direction if direction prop is set to ltr', async () => {
34-
setup({ rows, columns, direction: 'ltr' });
34+
setup({ rows, columns, direction: 'ltr' }, true);
3535
await expect.element(getGrid()).toHaveAttribute('dir', 'ltr');
36-
await userEvent.tab();
36+
await tabIntoGrid();
3737
await expect.element(getSelectedCell()).toHaveTextContent('ID');
3838
await userEvent.keyboard('{ArrowRight}');
3939
await expect.element(getSelectedCell()).toHaveTextContent('Name');
4040
});
4141

4242
test('should use right to left direction if direction prop is set to rtl', async () => {
43-
setup({ rows, columns, direction: 'rtl' });
43+
setup({ rows, columns, direction: 'rtl' }, true);
4444
await expect.element(getGrid()).toHaveAttribute('dir', 'rtl');
45-
await userEvent.tab();
45+
await tabIntoGrid();
4646
await expect.element(getSelectedCell()).toHaveTextContent('ID');
4747
await userEvent.keyboard('{ArrowLeft}');
4848
await expect.element(getSelectedCell()).toHaveTextContent('Name');

test/browser/keyboardNavigation.test.tsx

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getSelectedCell,
88
scrollGrid,
99
setup,
10+
tabIntoGrid,
1011
validateCellPosition
1112
} from './utils';
1213

@@ -27,13 +28,13 @@ const columns = [
2728
] as const satisfies Column<Row, Row>[];
2829

2930
test('keyboard navigation', async () => {
30-
setup({ columns, rows, topSummaryRows, bottomSummaryRows });
31+
setup({ columns, rows, topSummaryRows, bottomSummaryRows }, true);
3132

3233
// no initial selection
3334
await expect.element(getSelectedCell()).not.toBeInTheDocument();
3435

3536
// tab into the grid
36-
await userEvent.tab();
37+
await tabIntoGrid();
3738
validateCellPosition(0, 0);
3839

3940
// tab to the next cell
@@ -103,10 +104,10 @@ test('keyboard navigation', async () => {
103104
});
104105

105106
test('arrow and tab navigation', async () => {
106-
setup({ columns, rows, bottomSummaryRows });
107+
setup({ columns, rows, bottomSummaryRows }, true);
107108

108109
// pressing arrowleft on the leftmost cell does nothing
109-
await userEvent.tab();
110+
await tabIntoGrid();
110111
await userEvent.keyboard('{arrowdown}');
111112
validateCellPosition(0, 1);
112113
await userEvent.keyboard('{arrowleft}');
@@ -128,14 +129,7 @@ test('arrow and tab navigation', async () => {
128129
});
129130

130131
test('grid enter/exit', async () => {
131-
page.render(
132-
<>
133-
<button type="button">Before</button>
134-
<DataGrid columns={columns} rows={new Array(5)} bottomSummaryRows={bottomSummaryRows} />
135-
<br />
136-
<button type="button">After</button>
137-
</>
138-
);
132+
setup({ columns, rows: new Array(5), bottomSummaryRows }, true);
139133

140134
const beforeButton = page.getByRole('button', { name: 'Before' });
141135
const afterButton = page.getByRole('button', { name: 'After' });
@@ -144,8 +138,7 @@ test('grid enter/exit', async () => {
144138
await expect.element(getSelectedCell()).not.toBeInTheDocument();
145139

146140
// tab into the grid
147-
await userEvent.click(beforeButton);
148-
await userEvent.tab();
141+
await tabIntoGrid();
149142
validateCellPosition(0, 0);
150143

151144
// shift+tab tabs out of the grid if we are at the first cell
@@ -178,8 +171,8 @@ test('grid enter/exit', async () => {
178171
});
179172

180173
test('navigation with focusable cell renderer', async () => {
181-
setup({ columns, rows: new Array(1), bottomSummaryRows });
182-
await userEvent.tab();
174+
setup({ columns, rows: new Array(1), bottomSummaryRows }, true);
175+
await tabIntoGrid();
183176
await userEvent.keyboard('{arrowdown}');
184177
validateCellPosition(0, 1);
185178

@@ -219,8 +212,8 @@ test('navigation when header and summary rows have focusable elements', async ()
219212
}
220213
];
221214

222-
setup({ columns, rows: new Array(2), bottomSummaryRows });
223-
await userEvent.tab();
215+
setup({ columns, rows: new Array(2), bottomSummaryRows }, true);
216+
await tabIntoGrid();
224217

225218
// should set focus on the header filter
226219
expect(document.getElementById('header-filter1')).toHaveFocus();
@@ -259,8 +252,8 @@ test('navigation when selected cell not in the viewport', async () => {
259252
for (let i = 0; i < 99; i++) {
260253
columns.push({ key: `col${i}`, name: `col${i}`, frozen: i < 5 });
261254
}
262-
setup({ columns, rows, bottomSummaryRows });
263-
await userEvent.tab();
255+
setup({ columns, rows, bottomSummaryRows }, true);
256+
await tabIntoGrid();
264257
validateCellPosition(0, 0);
265258

266259
await userEvent.keyboard('{Control>}{end}{/Control}{arrowup}{arrowup}');
@@ -331,8 +324,8 @@ test('reset selected cell when row is removed', async () => {
331324
});
332325

333326
test('should not change the left and right arrow behavior for right to left languages', async () => {
334-
setup({ rows, columns, direction: 'rtl' });
335-
await userEvent.tab();
327+
setup({ rows, columns, direction: 'rtl' }, true);
328+
await tabIntoGrid();
336329
validateCellPosition(0, 0);
337330
await userEvent.tab();
338331
validateCellPosition(1, 0);

0 commit comments

Comments
 (0)