Skip to content

Commit 8351fb7

Browse files
committed
feat(): create boilerplate for challenge 64
1 parent 7cfe501 commit 8351fb7

27 files changed

Lines changed: 920 additions & 17 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ If you would like to propose a challenge, this project is open source, so feel f
2929
3030
## Challenges
3131

32-
Check [all 63 challenges](https://angular-challenges.vercel.app/)
32+
Check [all 64 challenges](https://angular-challenges.vercel.app/)
3333

3434
## Contributors ✨
3535

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"extends": ["../../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts"],
7+
"extends": [
8+
"plugin:@nx/angular",
9+
"plugin:@angular-eslint/template/process-inline-templates"
10+
],
11+
"rules": {
12+
"@angular-eslint/directive-selector": [
13+
"error",
14+
{
15+
"type": "attribute",
16+
"prefix": "app",
17+
"style": "camelCase"
18+
}
19+
],
20+
"@angular-eslint/component-selector": [
21+
"error",
22+
{
23+
"type": "element",
24+
"prefix": "app",
25+
"style": "kebab-case"
26+
}
27+
]
28+
}
29+
},
30+
{
31+
"files": ["*.html"],
32+
"extends": ["plugin:@nx/angular-template"],
33+
"rules": {}
34+
}
35+
]
36+
}

apps/forms/64-form-array/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# form-array
2+
3+
> author: thomas-laforge
4+
5+
### Run Application
6+
7+
```bash
8+
npx nx serve forms-form-array
9+
```
10+
11+
### Documentation and Instruction
12+
13+
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/forms/64-form-array/).
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
"name": "forms-form-array",
3+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
4+
"projectType": "application",
5+
"prefix": "app",
6+
"sourceRoot": "apps/forms/64-form-array/src",
7+
"tags": [],
8+
"targets": {
9+
"build": {
10+
"executor": "@angular/build:application",
11+
"outputs": ["{options.outputPath}"],
12+
"options": {
13+
"outputPath": "dist/apps/forms/64-form-array",
14+
"browser": "apps/forms/64-form-array/src/main.ts",
15+
"tsConfig": "apps/forms/64-form-array/tsconfig.app.json",
16+
"inlineStyleLanguage": "scss",
17+
"assets": [
18+
{
19+
"glob": "**/*",
20+
"input": "apps/forms/64-form-array/public"
21+
}
22+
],
23+
"styles": ["apps/forms/64-form-array/src/styles.scss"]
24+
},
25+
"configurations": {
26+
"production": {
27+
"budgets": [
28+
{
29+
"type": "initial",
30+
"maximumWarning": "500kb",
31+
"maximumError": "1mb"
32+
},
33+
{
34+
"type": "anyComponentStyle",
35+
"maximumWarning": "4kb",
36+
"maximumError": "8kb"
37+
}
38+
],
39+
"outputHashing": "all"
40+
},
41+
"development": {
42+
"optimization": false,
43+
"extractLicenses": false,
44+
"sourceMap": true
45+
}
46+
},
47+
"defaultConfiguration": "production"
48+
},
49+
"serve": {
50+
"continuous": true,
51+
"executor": "@angular/build:dev-server",
52+
"configurations": {
53+
"production": {
54+
"buildTarget": "forms-form-array:build:production"
55+
},
56+
"development": {
57+
"buildTarget": "forms-form-array:build:development"
58+
}
59+
},
60+
"defaultConfiguration": "development"
61+
},
62+
"lint": {
63+
"executor": "@nx/eslint:lint"
64+
},
65+
"serve-static": {
66+
"continuous": true,
67+
"executor": "@nx/web:file-server",
68+
"options": {
69+
"buildTarget": "forms-form-array:build",
70+
"staticFilePath": "dist/apps/forms/64-form-array/browser",
71+
"spa": true
72+
}
73+
}
74+
}
75+
}
14.7 KB
Binary file not shown.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { page, userEvent } from 'vitest/browser';
3+
import { AppComponent } from './app.component';
4+
5+
describe('AppComponent', () => {
6+
beforeEach(async () => {
7+
TestBed.createComponent(AppComponent);
8+
});
9+
10+
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+
33+
it('Then should show the form as incomplete initially', async () => {
34+
await expect
35+
.element(page.getByText(/form incomplete/i))
36+
.toBeInTheDocument();
37+
await expect
38+
.element(page.getByRole('button', { name: /submit/i }))
39+
.toBeEnabled();
40+
});
41+
});
42+
43+
describe('Given dynamic arrays', () => {
44+
it('Then should add and remove contacts and emails', async () => {
45+
await userEvent.click(page.getByRole('button', { name: /add contact/i }));
46+
await userEvent.click(page.getByRole('button', { name: /add contact/i }));
47+
await expect.element(page.getByText(/contact 2/i)).toBeInTheDocument();
48+
49+
await userEvent.click(
50+
page.getByRole('button', { name: /remove contact 2/i }),
51+
);
52+
await expect
53+
.element(page.getByText(/contact 2/i))
54+
.not.toBeInTheDocument();
55+
56+
await userEvent.click(page.getByRole('button', { name: /add email/i }));
57+
await userEvent.click(page.getByRole('button', { name: /add email/i }));
58+
await expect.element(page.getByText(/email 2/i)).toBeInTheDocument();
59+
60+
await userEvent.click(
61+
page.getByRole('button', { name: /remove email 2/i }),
62+
);
63+
await expect.element(page.getByText(/email 2/i)).not.toBeInTheDocument();
64+
});
65+
});
66+
67+
describe('Given valid form data', () => {
68+
it('Then should submit and display the submitted data', async () => {
69+
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');
75+
await userEvent.type(
76+
page.getByLabelText(/^Email\s*$/i),
77+
'jamie@example.com',
78+
);
79+
80+
await userEvent.click(page.getByRole('button', { name: /submit/i }));
81+
82+
await expect
83+
.element(page.getByRole('heading', { name: /submitted data/i }))
84+
.toBeInTheDocument();
85+
await expect
86+
.element(page.getByText(/"name": "Alex"/))
87+
.toBeInTheDocument();
88+
await expect.element(page.getByText(/"contacts":/)).toBeInTheDocument();
89+
});
90+
});
91+
92+
describe('Given invalid form data', () => {
93+
it('Then should show required errors on submit', async () => {
94+
await userEvent.click(page.getByRole('button', { name: /submit/i }));
95+
96+
await expect
97+
.element(page.getByText(/this field is required/i))
98+
.toBeInTheDocument();
99+
await expect
100+
.element(page.getByText(/at least one contact is required/i))
101+
.toBeInTheDocument();
102+
});
103+
104+
it('Then should show email format error for a contact', async () => {
105+
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');
110+
111+
await expect
112+
.element(page.getByText(/enter a valid email/i))
113+
.toBeInTheDocument();
114+
});
115+
});
116+
});

0 commit comments

Comments
 (0)