Skip to content

Commit a831a5c

Browse files
author
Fergus Bisset
committed
Fixes on act() errors in tests
1 parent a4fbc47 commit a831a5c

File tree

3 files changed

+116
-82
lines changed

3 files changed

+116
-82
lines changed

dist/meta/components.json

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"version": "0.0.44-alpha.2",
3-
"generatedAt": "2025-10-25T15:48:07.971Z",
2+
"version": "0.0.44-alpha.3",
3+
"generatedAt": "2025-10-25T15:48:59.144Z",
44
"count": 18,
55
"components": {
66
"Textarea": {
@@ -552,32 +552,44 @@
552552
],
553553
"source": "src/components/Input/Input.schema.ts"
554554
},
555-
"Hint": {
556-
"name": "Hint",
557-
"category": "typography",
555+
"Label": {
556+
"name": "Label",
557+
"category": "form",
558558
"since": "0.1.1",
559559
"a11yNotes": [
560-
"Provides supporting contextual help text associated with a form field or section."
560+
"Supports page heading mode by rendering an <h1> containing a nested <label> to retain association."
561561
],
562562
"props": [
563563
{
564-
"name": "id",
564+
"name": "htmlFor",
565565
"type": "string",
566-
"description": "HTML id attribute"
566+
"description": "ID of associated form control"
567567
},
568568
{
569569
"name": "className",
570570
"type": "string",
571571
"description": "Additional CSS classes"
572572
},
573+
{
574+
"name": "isPageHeading",
575+
"type": "boolean",
576+
"defaultValue": "false",
577+
"description": "Render as page heading (h1 wrapper)"
578+
},
579+
{
580+
"name": "size",
581+
"type": "'xl'|'l'|'m'|'s'",
582+
"defaultValue": "m",
583+
"description": "Size variant"
584+
},
573585
{
574586
"name": "children",
575587
"type": "ReactNode|string",
576588
"required": true,
577-
"description": "Hint content"
589+
"description": "Label content"
578590
}
579591
],
580-
"source": "src/components/Hint/Hint.schema.ts"
592+
"source": "src/components/Label/Label.schema.ts"
581593
},
582594
"Heading": {
583595
"name": "Heading",
@@ -625,6 +637,33 @@
625637
],
626638
"source": "src/components/Heading/Heading.schema.ts"
627639
},
640+
"Hint": {
641+
"name": "Hint",
642+
"category": "typography",
643+
"since": "0.1.1",
644+
"a11yNotes": [
645+
"Provides supporting contextual help text associated with a form field or section."
646+
],
647+
"props": [
648+
{
649+
"name": "id",
650+
"type": "string",
651+
"description": "HTML id attribute"
652+
},
653+
{
654+
"name": "className",
655+
"type": "string",
656+
"description": "Additional CSS classes"
657+
},
658+
{
659+
"name": "children",
660+
"type": "ReactNode|string",
661+
"required": true,
662+
"description": "Hint content"
663+
}
664+
],
665+
"source": "src/components/Hint/Hint.schema.ts"
666+
},
628667
"Fieldset": {
629668
"name": "Fieldset",
630669
"category": "form",
@@ -1072,45 +1111,6 @@
10721111
}
10731112
],
10741113
"source": "src/components/Button/Button.schema.ts"
1075-
},
1076-
"Label": {
1077-
"name": "Label",
1078-
"category": "form",
1079-
"since": "0.1.1",
1080-
"a11yNotes": [
1081-
"Supports page heading mode by rendering an <h1> containing a nested <label> to retain association."
1082-
],
1083-
"props": [
1084-
{
1085-
"name": "htmlFor",
1086-
"type": "string",
1087-
"description": "ID of associated form control"
1088-
},
1089-
{
1090-
"name": "className",
1091-
"type": "string",
1092-
"description": "Additional CSS classes"
1093-
},
1094-
{
1095-
"name": "isPageHeading",
1096-
"type": "boolean",
1097-
"defaultValue": "false",
1098-
"description": "Render as page heading (h1 wrapper)"
1099-
},
1100-
{
1101-
"name": "size",
1102-
"type": "'xl'|'l'|'m'|'s'",
1103-
"defaultValue": "m",
1104-
"description": "Size variant"
1105-
},
1106-
{
1107-
"name": "children",
1108-
"type": "ReactNode|string",
1109-
"required": true,
1110-
"description": "Label content"
1111-
}
1112-
],
1113-
"source": "src/components/Label/Label.schema.ts"
11141114
}
11151115
},
11161116
"categories": {
@@ -1119,12 +1119,12 @@
11191119
"Select",
11201120
"Radios",
11211121
"Input",
1122+
"Label",
11221123
"Fieldset",
11231124
"ErrorSummary",
11241125
"ErrorMessage",
11251126
"DateInput",
1126-
"Button",
1127-
"Label"
1127+
"Button"
11281128
],
11291129
"meta": [
11301130
"Tag"
@@ -1135,8 +1135,8 @@
11351135
"Details"
11361136
],
11371137
"typography": [
1138-
"Hint",
1139-
"Heading"
1138+
"Heading",
1139+
"Hint"
11401140
],
11411141
"uncategorised": [
11421142
"Checkboxes",

src/components/StepByStepNavigation/StepByStepNavigation.client.test.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect } from 'vitest';
22
import '@testing-library/jest-dom/vitest';
3-
import { render, screen } from '@testing-library/react';
3+
import { render, screen, act } from '@testing-library/react';
44
import { StepByStepNavigation } from './StepByStepNavigation';
55

66
const items = [
@@ -26,12 +26,16 @@ describe('StepByStepNavigation (client)', () => {
2626
const bBtn = screen.getAllByRole('button').find((b) => b.getAttribute('aria-label')?.includes('B'))!;
2727

2828
// Expand A
29-
aBtn.click();
29+
act(() => {
30+
aBtn.click();
31+
});
3032
expect(await screen.findByText('alpha')).toBeInTheDocument();
3133
expect(screen.queryByText('beta')).not.toBeInTheDocument();
3234

3335
// Expand B -> A should close
34-
bBtn.click();
36+
act(() => {
37+
bBtn.click();
38+
});
3539
expect(await screen.findByText('beta')).toBeInTheDocument();
3640
expect(screen.queryByText('alpha')).not.toBeInTheDocument();
3741
});

src/components/WorkflowSplitView/WorkflowSplitView.desktop.keyboard.client.test.tsx

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect, vi, beforeEach } from "vitest";
2-
import { render, screen, fireEvent, within, waitFor } from "@testing-library/react";
2+
import { render, screen, fireEvent, within, waitFor, act } from "@testing-library/react";
33
import React, { useState } from "react";
44
import { WorkflowSplitView } from "./WorkflowSplitView";
55

@@ -47,7 +47,7 @@ describe("WorkflowSplitView (desktop keyboard navigation)", () => {
4747
expect(gridcells.length).toBeGreaterThanOrEqual(2);
4848
});
4949

50-
it("roves focus across containers with ArrowRight/Left and Home/End", () => {
50+
it("roves focus across containers with ArrowRight/Left and Home/End", async () => {
5151
render(
5252
<WorkflowSplitView
5353
steps={steps}
@@ -65,20 +65,30 @@ describe("WorkflowSplitView (desktop keyboard navigation)", () => {
6565
const grid = document.querySelector(".nhsfdp-workflow-grid.panes-3") as HTMLElement;
6666
const cells = Array.from(grid.querySelectorAll('[role="gridcell"]')) as HTMLElement[];
6767
// focus first container
68-
cells[0].focus();
69-
70-
fireEvent.keyDown(cells[0], { key: "ArrowRight" });
71-
expect(document.activeElement).toBe(cells[1]);
72-
73-
fireEvent.keyDown(document.activeElement as Element, { key: "End" });
74-
expect(document.activeElement).toBe(cells[cells.length - 1]);
75-
76-
fireEvent.keyDown(document.activeElement as Element, { key: "Home" });
77-
expect(document.activeElement).toBe(cells[0]);
78-
79-
fireEvent.keyDown(document.activeElement as Element, { key: "ArrowLeft" });
68+
act(() => {
69+
cells[0].focus();
70+
});
71+
72+
act(() => {
73+
fireEvent.keyDown(cells[0], { key: "ArrowRight" });
74+
});
75+
await waitFor(() => expect(document.activeElement).toBe(cells[1]));
76+
77+
act(() => {
78+
fireEvent.keyDown(document.activeElement as Element, { key: "End" });
79+
});
80+
await waitFor(() => expect(document.activeElement).toBe(cells[cells.length - 1]));
81+
82+
act(() => {
83+
fireEvent.keyDown(document.activeElement as Element, { key: "Home" });
84+
});
85+
await waitFor(() => expect(document.activeElement).toBe(cells[0]));
86+
87+
act(() => {
88+
fireEvent.keyDown(document.activeElement as Element, { key: "ArrowLeft" });
89+
});
8090
// Should remain on first
81-
expect(document.activeElement).toBe(cells[0]);
91+
await waitFor(() => expect(document.activeElement).toBe(cells[0]));
8292
});
8393

8494
it("Enter enters nav container and focuses listbox active option; Escape ascends back to container", () => {
@@ -97,9 +107,13 @@ describe("WorkflowSplitView (desktop keyboard navigation)", () => {
97107

98108
const grid = document.querySelector(".nhsfdp-workflow-grid.panes-3") as HTMLElement;
99109
const navCell = grid.querySelector('[role="gridcell"][aria-label="Primary navigation"]') as HTMLElement;
100-
navCell.focus();
110+
act(() => {
111+
navCell.focus();
112+
});
101113

102-
fireEvent.keyDown(navCell, { key: "Enter" });
114+
act(() => {
115+
fireEvent.keyDown(navCell, { key: "Enter" });
116+
});
103117

104118
const listbox = within(navCell).getByRole("listbox");
105119
expect(listbox).toBeTruthy();
@@ -112,7 +126,9 @@ describe("WorkflowSplitView (desktop keyboard navigation)", () => {
112126
}
113127

114128
// Escape should move focus back to the nav container
115-
fireEvent.keyDown(listbox, { key: "Escape" });
129+
act(() => {
130+
fireEvent.keyDown(listbox, { key: "Escape" });
131+
});
116132
expect(document.activeElement).toBe(navCell);
117133
});
118134

@@ -134,16 +150,22 @@ describe("WorkflowSplitView (desktop keyboard navigation)", () => {
134150
const listbox = within(navCell).getByRole("listbox");
135151

136152
// Focus listbox and move through options (use ArrowDown twice to reach "Three")
137-
(listbox as HTMLElement).focus();
138-
fireEvent.keyDown(listbox, { key: "End" });
153+
act(() => {
154+
(listbox as HTMLElement).focus();
155+
});
156+
act(() => {
157+
fireEvent.keyDown(listbox, { key: "End" });
158+
});
139159

140160
const options = within(listbox).getAllByRole("option");
141161
expect(options.length).toBe(steps.length);
142162

143163
// Re-query the listbox to ensure we have a fresh live node, then activate via Enter
144164
const freshListbox = within(navCell).getByRole("listbox");
145-
fireEvent.keyDown(freshListbox, { key: "Enter", code: "Enter", keyCode: 13, charCode: 13 });
146-
fireEvent.keyUp(freshListbox, { key: "Enter", code: "Enter", keyCode: 13, charCode: 13 });
165+
act(() => {
166+
fireEvent.keyDown(freshListbox, { key: "Enter", code: "Enter", keyCode: 13, charCode: 13 });
167+
fireEvent.keyUp(freshListbox, { key: "Enter", code: "Enter", keyCode: 13, charCode: 13 });
168+
});
147169
// Re-query listbox post-activation to avoid stale references and assert aria-activedescendant
148170
await waitFor(() => {
149171
const freshListbox = within(navCell).getByRole("listbox");
@@ -173,8 +195,12 @@ describe("WorkflowSplitView (desktop keyboard navigation)", () => {
173195
const contentAside = screen.getByRole("gridcell", { name: /Breadcrumbs|Name|Two|One/ });
174196
const input = within(contentAside).getByLabelText("Name");
175197

176-
(input as HTMLElement).focus();
177-
fireEvent.keyDown(input, { key: "ArrowLeft" });
198+
act(() => {
199+
(input as HTMLElement).focus();
200+
});
201+
act(() => {
202+
fireEvent.keyDown(input, { key: "ArrowLeft" });
203+
});
178204

179205
// Focus remains in input
180206
expect(document.activeElement).toBe(input);
@@ -205,9 +231,13 @@ describe("WorkflowSplitView (desktop keyboard navigation)", () => {
205231
const secondaryCell = cells[2];
206232

207233
// Focus content container and move right
208-
contentCell.focus();
234+
act(() => {
235+
contentCell.focus();
236+
});
209237
expect(document.activeElement).toBe(contentCell);
210-
fireEvent.keyDown(contentCell, { key: "ArrowRight" });
238+
act(() => {
239+
fireEvent.keyDown(contentCell, { key: "ArrowRight" });
240+
});
211241

212242
// Focus should now be on the secondary container (content loses focus)
213243
await waitFor(() => expect(document.activeElement).toBe(secondaryCell));

0 commit comments

Comments
 (0)