@gradio/dataframe 0.24.2 → 0.24.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @gradio/dataframe
2
2
 
3
+ ## 0.24.4
4
+
5
+ ### Fixes
6
+
7
+ - [#13830](https://github.com/gradio-app/gradio/pull/13830) [`c398576`](https://github.com/gradio-app/gradio/commit/c39857647609361ea6548972ced702861b775b1f) - Restore the Dataframe's outer border. Thanks @hysts!
8
+ - [#13729](https://github.com/gradio-app/gradio/pull/13729) [`82ca180`](https://github.com/gradio-app/gradio/commit/82ca180c5c9a77d36b9097a62e63318317dc9d1f) - Fix Dataframe keyboard navigation accessibility. Thanks @dawoodkhan82!
9
+
10
+ ### Dependency updates
11
+
12
+ - @gradio/client@2.6.0
13
+ - @gradio/atoms@0.26.2
14
+ - @gradio/utils@0.14.1
15
+ - @gradio/upload@0.18.3
16
+ - @gradio/checkbox@0.8.3
17
+
18
+ ## 0.24.3
19
+
20
+ ### Dependency updates
21
+
22
+ - @gradio/client@2.5.1
23
+
24
+ ## 0.24.3
25
+
26
+ ### Fixes
27
+
28
+ - [#13744](https://github.com/gradio-app/gradio/pull/13744) [`ca9dec0`](https://github.com/gradio-app/gradio/commit/ca9dec0e2e07d33cf3551f5062ff35ab9acf3026) - Re-read the Dataframe virtual window whenever the row count changes. Thanks @abidlabs!
29
+
30
+ ## 0.24.2
31
+
32
+ ### Dependency updates
33
+
34
+ - @gradio/client@2.5.0
35
+
3
36
  ## 0.24.2
4
37
 
5
38
  ### Fixes
package/Dataframe.test.ts CHANGED
@@ -7,6 +7,8 @@ import {
7
7
  within
8
8
  } from "@self/tootils/render";
9
9
  import { tick } from "svelte";
10
+ import event from "@testing-library/user-event";
11
+ import { run_shared_prop_tests } from "@self/tootils/shared-prop-tests";
10
12
 
11
13
  import Dataframe from "./Index.svelte";
12
14
 
@@ -37,6 +39,14 @@ const default_props = {
37
39
  max_height: 500
38
40
  };
39
41
 
42
+ run_shared_prop_tests({
43
+ component: Dataframe,
44
+ name: "Dataframe",
45
+ base_props: default_props,
46
+ has_label: false,
47
+ has_validation_error: false
48
+ });
49
+
40
50
  function get_cell(container: HTMLElement, row: number, col: number) {
41
51
  return container.querySelector(
42
52
  `[data-row='${row}'][data-col='${col}']`
@@ -96,8 +106,36 @@ describe("Dataframe rendering", () => {
96
106
  expect(rows.length).toBe(3);
97
107
  });
98
108
 
109
+ test("resets inherited table padding so headers align with body cells", async () => {
110
+ const global_style = document.createElement("style");
111
+ global_style.textContent = "table { padding: 10px; }";
112
+ document.head.appendChild(global_style);
113
+
114
+ try {
115
+ const { container } = await render(Dataframe, default_props);
116
+ await wait();
117
+
118
+ const header_table = container.querySelector(
119
+ ".header-table"
120
+ ) as HTMLTableElement;
121
+ const first_header = get_header_cells(container)[0];
122
+ const first_body_cell = get_cell(container, 0, 0)!;
123
+ const header_rect = first_header.getBoundingClientRect();
124
+ const body_rect = first_body_cell.getBoundingClientRect();
125
+
126
+ expect(getComputedStyle(header_table).padding).toBe("0px");
127
+ expect(header_table.getBoundingClientRect().height).toBe(
128
+ header_rect.height
129
+ );
130
+ expect(header_rect.left).toBeCloseTo(body_rect.left);
131
+ expect(header_rect.right).toBeCloseTo(body_rect.right);
132
+ } finally {
133
+ global_style.remove();
134
+ }
135
+ });
136
+
99
137
  test("renders label when show_label is true", async () => {
100
- const { container } = await render(Dataframe, {
138
+ const { container, getByRole } = await render(Dataframe, {
101
139
  ...default_props,
102
140
  label: "Test Table",
103
141
  show_label: true
@@ -105,6 +143,9 @@ describe("Dataframe rendering", () => {
105
143
  await wait();
106
144
 
107
145
  expect(container.textContent).toContain("Test Table");
146
+ const grid = getByRole("grid");
147
+ expect(grid).toHaveAttribute("aria-label", "Test Table");
148
+ expect(grid.querySelector(".sr-only")).not.toBeInTheDocument();
108
149
  });
109
150
 
110
151
  test("renders row numbers when show_row_numbers is true", async () => {
@@ -116,6 +157,47 @@ describe("Dataframe rendering", () => {
116
157
 
117
158
  const row_number_cells = container.querySelectorAll(".row-number-cell");
118
159
  expect(row_number_cells.length).toBeGreaterThan(0);
160
+ expect(get_table_wrap(container)).toHaveAttribute("aria-colcount", "4");
161
+ expect(container.querySelector(".row-number-header")).toHaveAttribute(
162
+ "aria-colindex",
163
+ "1"
164
+ );
165
+ expect(get_header_cells(container)[0]).toHaveAttribute(
166
+ "aria-colindex",
167
+ "2"
168
+ );
169
+ const row_header = container.querySelector('[role="rowheader"]');
170
+ expect(row_header).toHaveAttribute("aria-colindex", "1");
171
+ expect(get_cell(container, 0, 0)).toHaveAttribute("aria-colindex", "2");
172
+ });
173
+
174
+ test("exposes grid rows through presentational layout wrappers", async () => {
175
+ const { getByRole } = await render(Dataframe, default_props);
176
+ await wait();
177
+
178
+ const grid = getByRole("grid");
179
+ const upload_container = grid.querySelector<HTMLElement>(
180
+ ":scope > .upload-container"
181
+ )!;
182
+ const viewport = upload_container.querySelector<HTMLElement>(
183
+ ":scope > .virtual-table-viewport"
184
+ )!;
185
+ const header_table = viewport.querySelector<HTMLElement>(
186
+ ":scope > .header-table"
187
+ )!;
188
+ const header_rowgroup = header_table.querySelector(":scope > thead");
189
+ const virtual_body = viewport.querySelector<HTMLElement>(
190
+ ":scope > .virtual-body"
191
+ )!;
192
+
193
+ expect(upload_container).toHaveAttribute("role", "none");
194
+ expect(upload_container).not.toHaveAttribute("tabindex");
195
+ expect(upload_container).not.toHaveAttribute("aria-label");
196
+ expect(upload_container).not.toHaveAttribute("aria-dropeffect");
197
+ expect(viewport).toHaveAttribute("role", "none");
198
+ expect(header_table).toHaveAttribute("role", "none");
199
+ expect(header_rowgroup).toHaveAttribute("role", "rowgroup");
200
+ expect(virtual_body).toHaveAttribute("role", "none");
119
201
  });
120
202
 
121
203
  test("renders search input when show_search is 'search'", async () => {
@@ -256,22 +338,6 @@ describe("Cell selection", () => {
256
338
  expect(next_cell.className).toContain("cell-selected");
257
339
  });
258
340
 
259
- test("Tab moves to next cell", async () => {
260
- const { container } = await render(Dataframe, default_props);
261
- await wait();
262
-
263
- const cell = get_cell(container, 0, 0)!;
264
- await fireEvent.mouseDown(cell);
265
- await wait();
266
-
267
- const table_wrap = get_table_wrap(container)!;
268
- await fireEvent.keyDown(table_wrap, { key: "Tab" });
269
- await wait();
270
-
271
- const next_cell = get_cell(container, 0, 1)!;
272
- expect(next_cell.className).toContain("cell-selected");
273
- });
274
-
275
341
  test("shift+click selects a range", async () => {
276
342
  const { container } = await render(Dataframe, default_props);
277
343
  await wait();
@@ -317,6 +383,389 @@ describe("Cell selection", () => {
317
383
  });
318
384
  });
319
385
 
386
+ describe("Keyboard accessibility", () => {
387
+ const external_buttons: HTMLButtonElement[] = [];
388
+
389
+ function append_external_button(text: string): HTMLButtonElement {
390
+ const button = document.createElement("button");
391
+ button.textContent = text;
392
+ document.body.appendChild(button);
393
+ external_buttons.push(button);
394
+ return button;
395
+ }
396
+
397
+ afterEach(() => {
398
+ cleanup();
399
+ external_buttons.forEach((button) => button.remove());
400
+ external_buttons.length = 0;
401
+ });
402
+
403
+ const navigation_props = {
404
+ ...default_props,
405
+ buttons: [] as string[]
406
+ };
407
+
408
+ test("Tab bypasses the grid container and enters on a single active cell", async () => {
409
+ const before = append_external_button("Before dataframe");
410
+
411
+ const { getByRole, getByTestId } = await render(
412
+ Dataframe,
413
+ navigation_props
414
+ );
415
+ const first_cell = await waitFor(() => getByTestId("cell-0-0"));
416
+ const second_cell = getByTestId("cell-0-1");
417
+
418
+ before.focus();
419
+ await event.tab();
420
+
421
+ expect(first_cell).toHaveFocus();
422
+ expect(first_cell).toHaveAttribute("tabindex", "0");
423
+ expect(second_cell).toHaveAttribute("tabindex", "-1");
424
+ const grid = getByRole("grid");
425
+ expect(grid).toHaveAttribute("tabindex", "-1");
426
+ expect(grid).not.toHaveFocus();
427
+ expect(grid).toHaveAttribute("aria-rowcount", "4");
428
+ expect(grid).toHaveAttribute("aria-colcount", "3");
429
+ });
430
+
431
+ test("Tab and Shift+Tab leave the grid in navigation mode", async () => {
432
+ const before = append_external_button("Before dataframe");
433
+
434
+ const { getByTestId } = await render(Dataframe, navigation_props);
435
+ const after = append_external_button("After dataframe");
436
+ const first_cell = await waitFor(() => getByTestId("cell-0-0"));
437
+
438
+ first_cell.focus();
439
+ await event.tab();
440
+ expect(after).toHaveFocus();
441
+
442
+ first_cell.focus();
443
+ await event.tab({ shift: true });
444
+ expect(before).toHaveFocus();
445
+ });
446
+
447
+ test("arrow keys move DOM focus between cells and headers", async () => {
448
+ const { getByTestId } = await render(Dataframe, navigation_props);
449
+ const first_cell = await waitFor(() => getByTestId("cell-0-0"));
450
+ const second_cell = getByTestId("cell-0-1");
451
+ const second_header = getByTestId("header-1");
452
+
453
+ first_cell.focus();
454
+ await event.keyboard("{ArrowRight}");
455
+ expect(second_cell).toHaveFocus();
456
+
457
+ await event.keyboard("{ArrowUp}");
458
+ expect(second_header).toHaveFocus();
459
+
460
+ await event.keyboard("{ArrowDown}");
461
+ expect(second_cell).toHaveFocus();
462
+ });
463
+
464
+ test("Space does not change a static boolean column from its header", async () => {
465
+ const { getByTestId, listen } = await render(Dataframe, {
466
+ ...navigation_props,
467
+ value: {
468
+ data: [[true], [false], [true]],
469
+ headers: ["Flag"],
470
+ metadata: null
471
+ },
472
+ datatype: ["bool"] as const,
473
+ static_columns: [0],
474
+ col_count: [1, "fixed"] as [number, "fixed"],
475
+ row_count: [3, "fixed"] as [number, "fixed"]
476
+ });
477
+ const change = listen("change");
478
+ const cell = await waitFor(() => getByTestId("cell-0-0"));
479
+ const header = getByTestId("header-0");
480
+
481
+ cell.focus();
482
+ await event.keyboard("{ArrowUp}");
483
+ expect(header).toHaveFocus();
484
+ await event.keyboard(" ");
485
+
486
+ expect(change).not.toHaveBeenCalled();
487
+ expect(header).toHaveFocus();
488
+ });
489
+
490
+ test("static headers named by label never enter edit mode", async () => {
491
+ const { getByTestId, queryByRole } = await render(Dataframe, {
492
+ ...navigation_props,
493
+ static_columns: ["Name"]
494
+ });
495
+ const cell = await waitFor(() => getByTestId("cell-0-0"));
496
+ const header = getByTestId("header-0");
497
+
498
+ cell.focus();
499
+ await event.keyboard("{ArrowUp}{Enter}");
500
+ expect(queryByRole("textbox", { name: "Cell is read-only" })).toBeNull();
501
+ expect(header).toHaveFocus();
502
+
503
+ await fireEvent.click(header);
504
+ await waitFor(() => expect(header).toHaveFocus());
505
+ expect(queryByRole("textbox", { name: "Cell is read-only" })).toBeNull();
506
+ });
507
+
508
+ test("clicking a read-only header selects and focuses it", async () => {
509
+ const { getByTestId, queryByRole } = await render(Dataframe, {
510
+ ...navigation_props,
511
+ interactive: false,
512
+ editable: false
513
+ });
514
+ const header = await waitFor(() => getByTestId("header-0"));
515
+
516
+ await fireEvent.click(header);
517
+
518
+ await waitFor(() => expect(header).toHaveFocus());
519
+ expect(header).toHaveAttribute("tabindex", "0");
520
+ expect(queryByRole("textbox")).toBeNull();
521
+ });
522
+
523
+ test("restores a cell tab stop when the selected header is removed", async () => {
524
+ const result = await render(Dataframe, navigation_props);
525
+ const last_cell = await waitFor(() => result.getByTestId("cell-0-2"));
526
+
527
+ last_cell.focus();
528
+ await event.keyboard("{ArrowUp}");
529
+ expect(result.getByTestId("header-2")).toHaveFocus();
530
+
531
+ await result.set_data({
532
+ value: {
533
+ data: [["Alice"], ["Bob"], ["Carol"]],
534
+ headers: ["Name"],
535
+ metadata: null
536
+ },
537
+ col_count: [1, "fixed"]
538
+ });
539
+
540
+ const first_cell = await waitFor(() => result.getByTestId("cell-0-0"));
541
+ expect(first_cell).toHaveAttribute("tabindex", "0");
542
+ expect(
543
+ result.getByRole("grid").querySelectorAll('[tabindex="0"]')
544
+ ).toHaveLength(1);
545
+ });
546
+
547
+ test("link key events are handled by the link instead of grid navigation", async () => {
548
+ const { getByTestId, queryByRole } = await render(Dataframe, {
549
+ ...navigation_props,
550
+ value: {
551
+ data: [['<a href="#model">Model</a>', "Details"]],
552
+ headers: ["Model", "Details"],
553
+ metadata: null
554
+ },
555
+ datatype: ["markdown", "str"] as const,
556
+ col_count: [2, "fixed"] as [number, "fixed"],
557
+ row_count: [1, "fixed"] as [number, "fixed"]
558
+ });
559
+ const first_cell = await waitFor(() => getByTestId("cell-0-0"));
560
+ const second_cell = getByTestId("cell-0-1");
561
+ const link = await waitFor(() => within(first_cell).getByRole("link"));
562
+
563
+ first_cell.focus();
564
+ await event.tab();
565
+ expect(link).toHaveFocus();
566
+
567
+ await fireEvent.keyDown(link, { key: "Enter" });
568
+ expect(queryByRole("textbox", { name: "Edit cell" })).toBeNull();
569
+ expect(link).toHaveFocus();
570
+
571
+ await fireEvent.keyDown(link, { key: "ArrowRight" });
572
+ expect(link).toHaveFocus();
573
+ expect(second_cell).not.toHaveFocus();
574
+ });
575
+
576
+ test("Home and End move to row and grid boundaries", async () => {
577
+ const { getByTestId } = await render(Dataframe, navigation_props);
578
+ const first_cell = await waitFor(() => getByTestId("cell-0-0"));
579
+
580
+ first_cell.focus();
581
+ await event.keyboard("{End}");
582
+ expect(getByTestId("cell-0-2")).toHaveFocus();
583
+
584
+ await event.keyboard("{Control>}{End}{/Control}");
585
+ expect(getByTestId("cell-2-2")).toHaveFocus();
586
+
587
+ await event.keyboard("{Home}");
588
+ expect(getByTestId("cell-2-0")).toHaveFocus();
589
+
590
+ await event.keyboard("{Control>}{Home}{/Control}");
591
+ expect(first_cell).toHaveFocus();
592
+ });
593
+
594
+ test("Enter activates the selected cell, enters editing, and commits back to grid navigation", async () => {
595
+ const { getByRole, getByTestId, queryByRole, listen } = await render(
596
+ Dataframe,
597
+ navigation_props
598
+ );
599
+ const select = listen("select");
600
+ const first_cell = await waitFor(() => getByTestId("cell-0-0"));
601
+
602
+ first_cell.focus();
603
+ await event.keyboard("{Enter}");
604
+
605
+ const editor = getByRole("textbox", { name: "Edit cell" });
606
+ await waitFor(() => expect(editor).toHaveFocus());
607
+ expect(select).toHaveBeenCalledWith({
608
+ index: [0, 0],
609
+ value: "Alice",
610
+ row_value: ["Alice", "30", "Engineer"],
611
+ col_value: ["Alice", "Bob", "Carol"]
612
+ });
613
+
614
+ await event.keyboard("{Enter}");
615
+ expect(
616
+ queryByRole("textbox", { name: "Edit cell" })
617
+ ).not.toBeInTheDocument();
618
+ expect(first_cell).toHaveFocus();
619
+ });
620
+
621
+ test("Space activates a cell without entering edit mode", async () => {
622
+ const { getByTestId, queryByRole, listen } = await render(
623
+ Dataframe,
624
+ navigation_props
625
+ );
626
+ const select = listen("select");
627
+ const first_cell = await waitFor(() => getByTestId("cell-0-0"));
628
+
629
+ first_cell.focus();
630
+ await event.keyboard(" ");
631
+
632
+ expect(select).toHaveBeenCalledTimes(1);
633
+ expect(
634
+ queryByRole("textbox", { name: "Edit cell" })
635
+ ).not.toBeInTheDocument();
636
+ expect(first_cell).toHaveFocus();
637
+ });
638
+
639
+ test("Space toggles an editable boolean cell", async () => {
640
+ const { getByTestId } = await render(Dataframe, {
641
+ ...navigation_props,
642
+ value: {
643
+ data: [[false]],
644
+ headers: ["Enabled"],
645
+ metadata: null
646
+ },
647
+ datatype: ["bool"] as const,
648
+ col_count: [1, "fixed"] as [number, "fixed"],
649
+ row_count: [1, "fixed"] as [number, "fixed"]
650
+ });
651
+ const cell = await waitFor(() => getByTestId("cell-0-0"));
652
+ const checkbox = within(cell).getByTestId("checkbox");
653
+
654
+ cell.focus();
655
+ await event.keyboard(" ");
656
+
657
+ await waitFor(() => expect(checkbox).toBeChecked());
658
+ expect(cell).toHaveFocus();
659
+ });
660
+
661
+ test("F2 toggles edit mode and Escape returns to grid navigation", async () => {
662
+ const { getByRole, getByTestId, queryByRole } = await render(
663
+ Dataframe,
664
+ navigation_props
665
+ );
666
+ const first_cell = await waitFor(() => getByTestId("cell-0-0"));
667
+
668
+ first_cell.focus();
669
+ await event.keyboard("{F2}");
670
+ await waitFor(() =>
671
+ expect(getByRole("textbox", { name: "Edit cell" })).toHaveFocus()
672
+ );
673
+ await event.keyboard("{F2}");
674
+ expect(
675
+ queryByRole("textbox", { name: "Edit cell" })
676
+ ).not.toBeInTheDocument();
677
+ expect(first_cell).toHaveFocus();
678
+
679
+ await event.keyboard("{F2}");
680
+ await waitFor(() =>
681
+ expect(getByRole("textbox", { name: "Edit cell" })).toHaveFocus()
682
+ );
683
+
684
+ await event.keyboard("{Escape}");
685
+ expect(
686
+ queryByRole("textbox", { name: "Edit cell" })
687
+ ).not.toBeInTheDocument();
688
+ expect(first_cell).toHaveFocus();
689
+ });
690
+
691
+ test("Tab moves between cells while editing and leaves at the grid boundary", async () => {
692
+ const before = append_external_button("Before dataframe");
693
+ const { getByRole, getByTestId } = await render(
694
+ Dataframe,
695
+ navigation_props
696
+ );
697
+ const after = append_external_button("After dataframe");
698
+ const first_cell = await waitFor(() => getByTestId("cell-0-0"));
699
+ const last_cell = getByTestId("cell-2-2");
700
+
701
+ first_cell.focus();
702
+ await event.keyboard("{Enter}");
703
+ await waitFor(() =>
704
+ expect(getByRole("textbox", { name: "Edit cell" })).toHaveFocus()
705
+ );
706
+ await event.tab();
707
+ await waitFor(() =>
708
+ expect(getByRole("textbox", { name: "Edit cell" })).toHaveFocus()
709
+ );
710
+ expect(getByTestId("cell-0-1")).toHaveAttribute("tabindex", "0");
711
+
712
+ last_cell.focus();
713
+ await event.keyboard("{Enter}");
714
+ await event.tab();
715
+ expect(after).toHaveFocus();
716
+
717
+ first_cell.focus();
718
+ await event.keyboard("{Enter}");
719
+ await waitFor(() =>
720
+ expect(getByRole("textbox", { name: "Edit cell" })).toHaveFocus()
721
+ );
722
+ await event.tab({ shift: true });
723
+ expect(before).toHaveFocus();
724
+ });
725
+
726
+ test("arrow navigation follows visible filtered rows while select reports original indices", async () => {
727
+ const filtered_props = {
728
+ ...navigation_props,
729
+ value: {
730
+ data: [["match one"], ["skip"], ["match two"]],
731
+ headers: ["Value"],
732
+ metadata: null
733
+ },
734
+ col_count: [1, "fixed"] as [number, "fixed"],
735
+ row_count: [3, "fixed"] as [number, "fixed"],
736
+ show_search: "search" as const
737
+ };
738
+ const { getByPlaceholderText, getByTestId, queryByTestId, listen } =
739
+ await render(Dataframe, filtered_props);
740
+ const search = getByPlaceholderText("Search...");
741
+
742
+ await event.type(search, "match");
743
+ await waitFor(() => {
744
+ expect(queryByTestId("cell-1-0")).not.toBeInTheDocument();
745
+ });
746
+
747
+ const first_match = getByTestId("cell-0-0");
748
+ const second_match = getByTestId("cell-2-0");
749
+ first_match.focus();
750
+ await event.keyboard("{ArrowDown}");
751
+ expect(second_match).toHaveFocus();
752
+ expect(second_match).toHaveAttribute("aria-rowindex", "3");
753
+
754
+ const select = listen("select");
755
+ await event.keyboard(" ");
756
+ expect(select).toHaveBeenCalledWith({
757
+ index: [2, 0],
758
+ value: "match two",
759
+ row_value: ["match two"],
760
+ col_value: ["match one", "skip", "match two"]
761
+ });
762
+ });
763
+
764
+ test.todo(
765
+ "VISUAL: keyboard focus shows a visible outline around the active Dataframe cell — needs Playwright visual regression screenshot comparison"
766
+ );
767
+ });
768
+
320
769
  describe("Header overflow", () => {
321
770
  let wrapper: HTMLDivElement;
322
771
 
@@ -487,20 +936,23 @@ describe("Add/remove rows and columns", () => {
487
936
  row_count: [3, "dynamic"] as [number, "fixed" | "dynamic"]
488
937
  };
489
938
 
490
- test("add row button appends a new row", async () => {
491
- const { container } = await render(Dataframe, dynamic_props);
939
+ test("add row button appends a row and focuses its first cell", async () => {
940
+ const { container, getByRole, getByTestId } = await render(Dataframe, {
941
+ ...dynamic_props,
942
+ value: {
943
+ data: [],
944
+ headers: default_props.value.headers,
945
+ metadata: null
946
+ },
947
+ row_count: [0, "dynamic"] as [number, "dynamic"]
948
+ });
492
949
  await wait();
493
950
 
494
- const initial_rows = get_rows(container).length;
495
-
496
- // The empty row button should be present for dynamic row_count
497
- const add_btn = container.querySelector(".empty-row-button") as HTMLElement;
498
- if (add_btn) {
499
- await fireEvent.click(add_btn);
500
- await wait();
951
+ await fireEvent.click(getByRole("button", { name: "Add row" }));
501
952
 
502
- expect(get_rows(container).length).toBe(initial_rows + 1);
503
- }
953
+ const added_cell = await waitFor(() => getByTestId("cell-0-0"));
954
+ expect(get_rows(container)).toHaveLength(1);
955
+ expect(added_cell).toHaveFocus();
504
956
  });
505
957
 
506
958
  // Cell menu add row tests: The CellMenu renders outside the table-wrap parent,
@@ -511,7 +963,7 @@ describe("Add/remove rows and columns", () => {
511
963
  test.todo("add row below via cell menu");
512
964
 
513
965
  test("delete row via cell menu", async () => {
514
- const { container } = await render(Dataframe, dynamic_props);
966
+ const { container, getByTestId } = await render(Dataframe, dynamic_props);
515
967
  await wait();
516
968
 
517
969
  const cell = get_cell(container, 1, 0)!;
@@ -531,10 +983,11 @@ describe("Add/remove rows and columns", () => {
531
983
  await wait();
532
984
 
533
985
  expect(get_rows(container).length).toBe(2);
986
+ await waitFor(() => expect(getByTestId("cell-1-0")).toHaveFocus());
534
987
  });
535
988
 
536
989
  test("add column via header menu", async () => {
537
- const { container } = await render(Dataframe, dynamic_props);
990
+ const { container, getByTestId } = await render(Dataframe, dynamic_props);
538
991
  await wait();
539
992
 
540
993
  const initial_headers = get_header_cells(container).length;
@@ -543,24 +996,24 @@ describe("Add/remove rows and columns", () => {
543
996
  const headers = get_header_cells(container);
544
997
  const header = headers[0] as HTMLElement;
545
998
  const menu_btn = header.querySelector(".cell-menu-button") as HTMLElement;
546
- if (menu_btn) {
547
- await fireEvent.click(menu_btn);
548
- await wait();
999
+ expect(menu_btn).toBeTruthy();
1000
+ await fireEvent.click(menu_btn);
1001
+ await wait();
549
1002
 
550
- const add_col_btn = document.querySelector(
551
- '[aria-label="Add column to the right"]'
552
- ) as HTMLElement;
553
- if (add_col_btn) {
554
- await fireEvent.click(add_col_btn);
555
- await wait();
1003
+ const add_col_btn = document.querySelector(
1004
+ '[aria-label="Add column to the right"]'
1005
+ ) as HTMLElement;
1006
+ expect(add_col_btn).toBeTruthy();
1007
+ await fireEvent.click(add_col_btn);
556
1008
 
557
- expect(get_header_cells(container).length).toBe(initial_headers + 1);
558
- }
559
- }
1009
+ await waitFor(() =>
1010
+ expect(get_header_cells(container)).toHaveLength(initial_headers + 1)
1011
+ );
1012
+ expect(getByTestId("header-1")).toHaveFocus();
560
1013
  });
561
1014
 
562
1015
  test("delete column via header menu", async () => {
563
- const { container } = await render(Dataframe, dynamic_props);
1016
+ const { container, getByTestId } = await render(Dataframe, dynamic_props);
564
1017
  await wait();
565
1018
 
566
1019
  const initial_headers = get_header_cells(container).length;
@@ -580,6 +1033,7 @@ describe("Add/remove rows and columns", () => {
580
1033
  await wait();
581
1034
 
582
1035
  expect(get_header_cells(container).length).toBe(initial_headers - 1);
1036
+ await waitFor(() => expect(getByTestId("cell-0-0")).toHaveFocus());
583
1037
  }
584
1038
  }
585
1039
  });