Skip to content

Commit 765a1c0

Browse files
committed
feat(): add tests
1 parent 8351fb7 commit 765a1c0

File tree

4 files changed

+79
-71
lines changed

4 files changed

+79
-71
lines changed

apps/forms/64-form-array/src/app/app.component.spec.ts

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,6 @@ describe('AppComponent', () => {
88
});
99

1010
describe('When component is rendered', () => {
11-
it('Then should display headings and required fields', async () => {
12-
await expect
13-
.element(page.getByRole('heading', { name: /registration form/i }))
14-
.toBeInTheDocument();
15-
await expect
16-
.element(page.getByRole('heading', { name: /profile/i }))
17-
.toBeInTheDocument();
18-
await expect
19-
.element(page.getByRole('heading', { name: /contacts/i }))
20-
.toBeInTheDocument();
21-
await expect
22-
.element(page.getByRole('heading', { name: /emails/i }))
23-
.toBeInTheDocument();
24-
25-
await expect
26-
.element(page.getByLabelText(/^Name\s*$/i))
27-
.toBeInTheDocument();
28-
await expect
29-
.element(page.getByLabelText(/^Pseudo\s*$/i))
30-
.toBeInTheDocument();
31-
});
32-
3311
it('Then should show the form as incomplete initially', async () => {
3412
await expect
3513
.element(page.getByText(/form incomplete/i))
@@ -67,13 +45,28 @@ describe('AppComponent', () => {
6745
describe('Given valid form data', () => {
6846
it('Then should submit and display the submitted data', async () => {
6947
await userEvent.click(page.getByRole('button', { name: /add contact/i }));
70-
await userEvent.type(page.getByLabelText(/^Name\s*$/i), 'Alex');
71-
await userEvent.type(page.getByLabelText(/^Pseudo\s*$/i), 'Nexus');
72-
await userEvent.type(page.getByLabelText(/^First name\s*$/i), 'Jamie');
73-
await userEvent.type(page.getByLabelText(/^Last name\s*$/i), 'Doe');
74-
await userEvent.type(page.getByLabelText(/^Relation\s*$/i), 'Friend');
7548
await userEvent.type(
76-
page.getByLabelText(/^Email\s*$/i),
49+
page.getByRole('textbox', { name: /^Name\s*$/i }),
50+
'Alex',
51+
);
52+
await userEvent.type(
53+
page.getByRole('textbox', { name: /^Pseudo\s*$/i }),
54+
'Nexus',
55+
);
56+
await userEvent.type(
57+
page.getByRole('textbox', { name: /^First name\s*$/i }),
58+
'Jamie',
59+
);
60+
await userEvent.type(
61+
page.getByRole('textbox', { name: /^Last name\s*$/i }),
62+
'Doe',
63+
);
64+
await userEvent.type(
65+
page.getByRole('textbox', { name: /^Relation\s*$/i }),
66+
'Friend',
67+
);
68+
await userEvent.type(
69+
page.getByRole('textbox', { name: /^Email\s*$/i }),
7770
'jamie@example.com',
7871
);
7972

@@ -94,19 +87,64 @@ describe('AppComponent', () => {
9487
await userEvent.click(page.getByRole('button', { name: /submit/i }));
9588

9689
await expect
97-
.element(page.getByText(/this field is required/i))
90+
.element(page.getByText(/Name\s*This field is required/i))
91+
.toBeInTheDocument();
92+
await expect
93+
.element(page.getByText(/Pseudo\s*This field is required/i))
9894
.toBeInTheDocument();
9995
await expect
10096
.element(page.getByText(/at least one contact is required/i))
10197
.toBeInTheDocument();
10298
});
10399

100+
it('Then should show contact required errors on submit', async () => {
101+
await userEvent.click(page.getByRole('button', { name: /add contact/i }));
102+
await userEvent.click(page.getByRole('button', { name: /submit/i }));
103+
104+
await expect
105+
.element(page.getByText(/email is required/i))
106+
.toBeInTheDocument();
107+
});
108+
104109
it('Then should show email format error for a contact', async () => {
105110
await userEvent.click(page.getByRole('button', { name: /add contact/i }));
106-
await userEvent.type(page.getByLabelText(/^First name\s*$/i), 'Jamie');
107-
await userEvent.type(page.getByLabelText(/^Last name\s*$/i), 'Doe');
108-
await userEvent.type(page.getByLabelText(/^Relation\s*$/i), 'Friend');
109-
await userEvent.type(page.getByLabelText(/^Email\s*$/i), 'invalid');
111+
await userEvent.type(
112+
page.getByRole('textbox', { name: /^First name\s*$/i }),
113+
'Jamie',
114+
);
115+
await userEvent.type(
116+
page.getByRole('textbox', { name: /^Last name\s*$/i }),
117+
'Doe',
118+
);
119+
await userEvent.type(
120+
page.getByRole('textbox', { name: /^Relation\s*$/i }),
121+
'Friend',
122+
);
123+
await userEvent.type(
124+
page.getByRole('textbox', { name: /^Email\s*$/i }),
125+
'invalid',
126+
);
127+
128+
await expect
129+
.element(page.getByText(/enter a valid email/i))
130+
.toBeInTheDocument();
131+
});
132+
133+
it('Then should show required errors for email entries on submit', async () => {
134+
await userEvent.click(page.getByRole('button', { name: /add email/i }));
135+
await userEvent.click(page.getByRole('button', { name: /submit/i }));
136+
137+
await expect
138+
.element(page.getByText(/email is required/i))
139+
.toBeInTheDocument();
140+
});
141+
142+
it('Then should show email format error for an email entry', async () => {
143+
await userEvent.click(page.getByRole('button', { name: /add email/i }));
144+
await userEvent.type(
145+
page.getByRole('textbox', { name: /^Email\s*$/i }),
146+
'invalid',
147+
);
110148

111149
await expect
112150
.element(page.getByText(/enter a valid email/i))

apps/forms/64-form-array/vitest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default defineConfig(({ mode }) => ({
99
test: {
1010
globals: true,
1111
setupFiles: ['src/test-setup.ts'],
12+
// testTimeout: 3000,
1213
// environment: 'jsdom',
1314
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
1415
reporters: ['default'],

docs/src/content/docs/challenges/forms/64-form-array.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ sidebar:
1313

1414
## Information
1515

16-
Dynamic forms often need repeating groups, like adding multiple contacts or social links. Angular Reactive Forms provides `FormArray` to manage these collections while keeping validation and structure predictable.
16+
You already built a registration form with Reactive Forms (FormGroup / FormControl / FormArray). Now you will refactor it to **Signal-based forms** while keeping the UX, validations, and dynamic add/remove behavior.
1717

1818
You can learn more about Signal-based forms in the [Angular documentation](https://angular.dev/guide/forms/signals-based-forms).
1919

@@ -28,14 +28,14 @@ The application contains a single-page registration form with:
2828
- Last name (required)
2929
- Relation (required)
3030
- Email (required, valid email)
31-
- **Network links** (FormArray)
32-
- Network name (required)
33-
- Handle (required)
31+
- **Emails** (FormArray)
32+
- Type (required)
33+
- Email (required, valid email)
3434

3535
Current behavior:
3636

37-
- Users can add or remove contacts and network links.
38-
- Submit is disabled until the form is valid.
37+
- Users can add or remove contacts and emails.
38+
- Submit marks all controls as touched and blocks submission when the form is invalid.
3939
- Validation errors appear when fields are touched/dirty or after submitting.
4040
- Submitted data is displayed as JSON after a successful submission.
4141

@@ -49,7 +49,7 @@ After completing the challenge, your form should:
4949

5050
- Use Signal-based forms instead of `FormGroup`, `FormControl`, and `FormArray`.
5151
- Keep the same validation rules for all fields.
52-
- Preserve add/remove behavior for contacts and network links.
52+
- Preserve add/remove behavior for contacts and emails.
5353
- Keep the submit gating and submitted data preview.
5454
- Pass all existing tests.
5555

plan-formArray.prompt.md

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)