front-end-dev-standards 1.0.0
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/README.md +167 -0
- package/config/default.json +8 -0
- package/package.json +40 -0
- package/scripts/npm-cmd.js +25 -0
- package/scripts/setup.js +280 -0
- package/scripts/validate-package.js +41 -0
- package/standards/angular.md +234 -0
- package/standards/architecture.md +263 -0
- package/standards/coding-style.md +223 -0
- package/standards/security.md +6 -0
- package/standards/testing.md +244 -0
- package/templates/company.mdc.template +92 -0
- package/templates/copilot-instructions.md.template +92 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Testing Standards
|
|
2
|
+
|
|
3
|
+
> Unit and integration testing conventions for Angular applications using Vitest.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Test Stack
|
|
8
|
+
|
|
9
|
+
| Tool | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| **Vitest** | Test runner (Angular 21 default) |
|
|
12
|
+
| **Angular TestBed** | Component/service integration |
|
|
13
|
+
| **ng-mocks** / manual stubs | Dependency mocking (when needed) |
|
|
14
|
+
|
|
15
|
+
Run tests:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm test
|
|
19
|
+
ng test
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## File Conventions
|
|
25
|
+
|
|
26
|
+
- Co-locate tests: `employee.service.ts` → `employee.service.spec.ts`
|
|
27
|
+
- Page tests: `employee-list.page.spec.ts`
|
|
28
|
+
- Place test utilities in `src/testing/` or feature `testing/` folder.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Service Tests
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { TestBed } from '@angular/core/testing';
|
|
36
|
+
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
|
37
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
38
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
39
|
+
|
|
40
|
+
import { EmployeeService } from './employee.service';
|
|
41
|
+
|
|
42
|
+
describe('EmployeeService', () => {
|
|
43
|
+
let service: EmployeeService;
|
|
44
|
+
let httpMock: HttpTestingController;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
TestBed.configureTestingModule({
|
|
48
|
+
providers: [EmployeeService, provideHttpClient(), provideHttpClientTesting()],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
service = TestBed.inject(EmployeeService);
|
|
52
|
+
httpMock = TestBed.inject(HttpTestingController);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
httpMock.verify();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should fetch all employees', () => {
|
|
60
|
+
const mockEmployees = [{ id: '1', firstName: 'Jane', lastName: 'Doe', email: 'jane@co.com', departmentId: 'd1', createdAt: '2024-01-01' }];
|
|
61
|
+
|
|
62
|
+
service.getAll().subscribe(employees => {
|
|
63
|
+
expect(employees).toEqual(mockEmployees);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const req = httpMock.expectOne('/api/employees');
|
|
67
|
+
expect(req.request.method).toBe('GET');
|
|
68
|
+
req.flush(mockEmployees);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Service Test Rules
|
|
74
|
+
|
|
75
|
+
- Use `HttpTestingController` for HTTP services.
|
|
76
|
+
- Test success and error paths.
|
|
77
|
+
- Verify request method, URL, headers, and body.
|
|
78
|
+
- No real HTTP calls in unit tests.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Component Tests
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
86
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
87
|
+
|
|
88
|
+
import { EmployeeListPage } from './employee-list.page';
|
|
89
|
+
import { EmployeeService } from '../../services/employee.service';
|
|
90
|
+
import { of, throwError } from 'rxjs';
|
|
91
|
+
|
|
92
|
+
describe('EmployeeListPage', () => {
|
|
93
|
+
let fixture: ComponentFixture<EmployeeListPage>;
|
|
94
|
+
let component: EmployeeListPage;
|
|
95
|
+
let employeeService: { getAll: ReturnType<typeof vi.fn> };
|
|
96
|
+
|
|
97
|
+
beforeEach(async () => {
|
|
98
|
+
employeeService = { getAll: vi.fn().mockReturnValue(of([])) };
|
|
99
|
+
|
|
100
|
+
await TestBed.configureTestingModule({
|
|
101
|
+
imports: [EmployeeListPage],
|
|
102
|
+
providers: [{ provide: EmployeeService, useValue: employeeService }],
|
|
103
|
+
}).compileComponents();
|
|
104
|
+
|
|
105
|
+
fixture = TestBed.createComponent(EmployeeListPage);
|
|
106
|
+
component = fixture.componentInstance;
|
|
107
|
+
fixture.detectChanges();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should create', () => {
|
|
111
|
+
expect(component).toBeTruthy();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should display employees when loaded', () => {
|
|
115
|
+
const employees = [{ id: '1', firstName: 'Jane', lastName: 'Doe', email: 'j@co.com', departmentId: 'd1', createdAt: '2024-01-01' }];
|
|
116
|
+
employeeService.getAll.mockReturnValue(of(employees));
|
|
117
|
+
|
|
118
|
+
component.loadEmployees();
|
|
119
|
+
fixture.detectChanges();
|
|
120
|
+
|
|
121
|
+
expect(component.employees()).toEqual(employees);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should set error signal on failure', () => {
|
|
125
|
+
employeeService.getAll.mockReturnValue(throwError(() => new Error('Network error')));
|
|
126
|
+
|
|
127
|
+
component.loadEmployees();
|
|
128
|
+
fixture.detectChanges();
|
|
129
|
+
|
|
130
|
+
expect(component.error()).toBeTruthy();
|
|
131
|
+
expect(component.loading()).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Component Test Rules
|
|
137
|
+
|
|
138
|
+
- Import standalone component in `TestBed.configureTestingModule({ imports: [...] })`.
|
|
139
|
+
- Mock services — don't test service logic in component tests.
|
|
140
|
+
- Test signal state, not internal implementation details.
|
|
141
|
+
- Query DOM for user-visible behavior when testing templates.
|
|
142
|
+
- Use `fixture.detectChanges()` after state updates.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Testing Signals
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
it('should compute filtered employees', () => {
|
|
150
|
+
component.employees.set([
|
|
151
|
+
{ id: '1', firstName: 'Jane', /* ... */ },
|
|
152
|
+
{ id: '2', firstName: 'John', /* ... */ },
|
|
153
|
+
]);
|
|
154
|
+
component.searchTerm.set('Jane');
|
|
155
|
+
|
|
156
|
+
expect(component.filteredEmployees()).toHaveLength(1);
|
|
157
|
+
expect(component.filteredEmployees()[0].firstName).toBe('Jane');
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Prefer testing public signal values over private fields.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Test Naming
|
|
166
|
+
|
|
167
|
+
Use descriptive `it` blocks:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// Good
|
|
171
|
+
it('should disable submit button when form is invalid')
|
|
172
|
+
it('should redirect to login when auth guard rejects unauthenticated user')
|
|
173
|
+
|
|
174
|
+
// Bad
|
|
175
|
+
it('should work')
|
|
176
|
+
it('test submit')
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Pattern: `should [expected behavior] when [condition]`
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Coverage Expectations
|
|
184
|
+
|
|
185
|
+
| Layer | Minimum Coverage |
|
|
186
|
+
|---|---|
|
|
187
|
+
| Services (business/API) | 80%+ |
|
|
188
|
+
| Utility functions | 90%+ |
|
|
189
|
+
| Smart/Page components | 70%+ |
|
|
190
|
+
| Presentational components | Key interactions covered |
|
|
191
|
+
| Guards / Interceptors | 100% of branches |
|
|
192
|
+
|
|
193
|
+
Focus on **behavior coverage**, not line-count gaming.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## What to Test
|
|
198
|
+
|
|
199
|
+
### Always Test
|
|
200
|
+
|
|
201
|
+
- Service HTTP calls (success + error)
|
|
202
|
+
- Form validation logic
|
|
203
|
+
- Guards and interceptors
|
|
204
|
+
- Complex computed signals
|
|
205
|
+
- User interactions (click, submit, navigation)
|
|
206
|
+
|
|
207
|
+
### Skip or Minimize
|
|
208
|
+
|
|
209
|
+
- Trivial getters with no logic
|
|
210
|
+
- Third-party library internals
|
|
211
|
+
- Static template text with no binding logic
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Test Data
|
|
216
|
+
|
|
217
|
+
Create factories in `testing/factories/`:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// testing/factories/employee.factory.ts
|
|
221
|
+
import { Employee } from '../../features/employees/models/employee.model';
|
|
222
|
+
|
|
223
|
+
export function createEmployee(overrides: Partial<Employee> = {}): Employee {
|
|
224
|
+
return {
|
|
225
|
+
id: 'emp-1',
|
|
226
|
+
firstName: 'Jane',
|
|
227
|
+
lastName: 'Doe',
|
|
228
|
+
email: 'jane.doe@company.com',
|
|
229
|
+
departmentId: 'dept-1',
|
|
230
|
+
createdAt: '2024-06-01T00:00:00.000Z',
|
|
231
|
+
...overrides,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Anti-Patterns
|
|
239
|
+
|
|
240
|
+
- ❌ Testing implementation details (private methods)
|
|
241
|
+
- ❌ Shared mutable state between tests
|
|
242
|
+
- ❌ `setTimeout` without fakeAsync / vi.useFakeTimers
|
|
243
|
+
- ❌ Snapshot testing for entire templates ( brittle )
|
|
244
|
+
- ❌ E2E tests disguised as unit tests
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Company Angular frontend coding standards — enforced for all AI-generated and reviewed code
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Company Frontend Standards (front-end-dev-standards v{{STANDARDS_VERSION}})
|
|
7
|
+
|
|
8
|
+
You are generating code for an enterprise Angular application. Follow these standards strictly.
|
|
9
|
+
|
|
10
|
+
> Auto-generated by front-end-dev-standards on {{GENERATED_AT}}.
|
|
11
|
+
> Do not edit this file manually unless your team owns custom overrides — use `.standardsrc.json` and re-run setup.
|
|
12
|
+
|
|
13
|
+
## Standards Documents
|
|
14
|
+
|
|
15
|
+
Read and apply ALL of the following standards files in the project:
|
|
16
|
+
|
|
17
|
+
{{STANDARDS_PATHS}}
|
|
18
|
+
|
|
19
|
+
When standards conflict, priority order is:
|
|
20
|
+
1. `angular.md` (framework-specific rules)
|
|
21
|
+
2. `architecture.md` (structure and patterns)
|
|
22
|
+
3. `coding-style.md` (naming and formatting)
|
|
23
|
+
4. `testing.md` (test requirements)
|
|
24
|
+
|
|
25
|
+
## Mandatory Angular Patterns (Angular 20+)
|
|
26
|
+
|
|
27
|
+
Always generate:
|
|
28
|
+
|
|
29
|
+
- **Standalone components** — no `NgModule` for new code
|
|
30
|
+
- **`inject()`** for dependency injection — never constructor injection
|
|
31
|
+
- **Signals** — `signal()`, `computed()`, `input()`, `output()` for state and bindings
|
|
32
|
+
- **`ChangeDetectionStrategy.OnPush`** on every component
|
|
33
|
+
- **Typed Reactive Forms** — `FormGroup<T>` with `nonNullable: true`
|
|
34
|
+
- **Angular Material** for UI components in enterprise apps
|
|
35
|
+
- **Feature-based folder structure** — see `architecture.md`
|
|
36
|
+
- **Native control flow** — `@if`, `@for`, `@switch` (not structural directives)
|
|
37
|
+
- **`track` in `@for` loops**
|
|
38
|
+
|
|
39
|
+
Never generate:
|
|
40
|
+
|
|
41
|
+
- ❌ `NgModule` declarations
|
|
42
|
+
- ❌ Constructor-based dependency injection
|
|
43
|
+
- ❌ `@Input()` / `@Output()` decorators (use signal inputs/outputs)
|
|
44
|
+
- ❌ `*ngIf`, `*ngFor`, `*ngSwitch`
|
|
45
|
+
- ❌ `BehaviorSubject` for simple component-local state
|
|
46
|
+
- ❌ Untyped forms or `any` without justification
|
|
47
|
+
- ❌ Business logic in templates
|
|
48
|
+
- ❌ Direct DOM manipulation
|
|
49
|
+
|
|
50
|
+
## Architecture Quick Reference
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
src/app/
|
|
54
|
+
core/ → guards, interceptors, app-wide services
|
|
55
|
+
shared/ → reusable UI components (no feature imports)
|
|
56
|
+
features/ → lazy-loaded business features
|
|
57
|
+
layout/ → app shell
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each feature:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
features/<name>/
|
|
64
|
+
components/ → presentational
|
|
65
|
+
pages/ → smart/routable containers
|
|
66
|
+
services/ → API integration
|
|
67
|
+
models/ → TypeScript interfaces
|
|
68
|
+
<name>.routes.ts
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Code Generation Checklist
|
|
72
|
+
|
|
73
|
+
Before finishing any generated code, verify:
|
|
74
|
+
|
|
75
|
+
1. Component is standalone with explicit `imports` array
|
|
76
|
+
2. OnPush change detection is set
|
|
77
|
+
3. Services use `inject()` and `providedIn: 'root'`
|
|
78
|
+
4. HTTP calls are typed: `http.get<ResponseType>()`
|
|
79
|
+
5. Errors are handled and surfaced to the user
|
|
80
|
+
6. A co-located `*.spec.ts` file exists for services and pages
|
|
81
|
+
7. File names use kebab-case; classes use PascalCase
|
|
82
|
+
|
|
83
|
+
## Testing Requirements
|
|
84
|
+
|
|
85
|
+
When generating services or page components, also generate Vitest tests:
|
|
86
|
+
|
|
87
|
+
- Use `TestBed.configureTestingModule({ imports: [StandaloneComponent] })`
|
|
88
|
+
- Mock dependencies with `vi.fn()`
|
|
89
|
+
- Test success and error paths
|
|
90
|
+
- Use `HttpTestingController` for HTTP services
|
|
91
|
+
|
|
92
|
+
See `.ai-standards/testing.md` for full testing standards.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# GitHub Copilot Instructions
|
|
2
|
+
|
|
3
|
+
> Auto-generated by front-end-dev-standards v{{STANDARDS_VERSION}} on {{GENERATED_AT}}.
|
|
4
|
+
|
|
5
|
+
Follow the company Angular frontend standards defined in this repository.
|
|
6
|
+
|
|
7
|
+
## Standards Location
|
|
8
|
+
|
|
9
|
+
{{STANDARDS_PATHS}}
|
|
10
|
+
|
|
11
|
+
Read these files before generating or suggesting code.
|
|
12
|
+
|
|
13
|
+
## Required Patterns (Angular 20+)
|
|
14
|
+
|
|
15
|
+
When generating Angular code, ALWAYS use:
|
|
16
|
+
|
|
17
|
+
- Standalone components (no NgModules)
|
|
18
|
+
- `inject()` instead of constructor injection
|
|
19
|
+
- Signals: `signal()`, `computed()`, `input()`, `output()`
|
|
20
|
+
- `ChangeDetectionStrategy.OnPush`
|
|
21
|
+
- Typed Reactive Forms with `FormGroup<T>` and `nonNullable: true`
|
|
22
|
+
- Angular Material for UI
|
|
23
|
+
- Native control flow: `@if`, `@for`, `@switch`
|
|
24
|
+
- `track` expression in all `@for` loops
|
|
25
|
+
- Feature-based architecture under `src/app/features/`
|
|
26
|
+
|
|
27
|
+
## Never Suggest
|
|
28
|
+
|
|
29
|
+
- NgModule-based components or declarations
|
|
30
|
+
- Constructor dependency injection
|
|
31
|
+
- `@Input()` / `@Output()` decorators (use signal-based APIs)
|
|
32
|
+
- `*ngIf`, `*ngFor`, `*ngSwitch` structural directives
|
|
33
|
+
- `BehaviorSubject` for simple UI state
|
|
34
|
+
- Untyped forms or `any` type
|
|
35
|
+
- Template-driven forms for complex forms
|
|
36
|
+
- Direct DOM access (`document.querySelector`, `ElementRef` manipulation)
|
|
37
|
+
|
|
38
|
+
## File Structure
|
|
39
|
+
|
|
40
|
+
When creating new features:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
src/app/features/<feature-name>/
|
|
44
|
+
components/ # presentational components
|
|
45
|
+
pages/ # routable smart components (*.page.ts)
|
|
46
|
+
services/ # API services
|
|
47
|
+
models/ # TypeScript interfaces
|
|
48
|
+
<feature>.routes.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Naming Conventions
|
|
52
|
+
|
|
53
|
+
- Files: `kebab-case` (e.g., `employee-list.component.ts`)
|
|
54
|
+
- Classes: `PascalCase` (e.g., `EmployeeListComponent`)
|
|
55
|
+
- Selectors: `app-` prefix (e.g., `app-employee-list`)
|
|
56
|
+
- Signals: descriptive nouns (`employees`, `loading`, `isValid`)
|
|
57
|
+
|
|
58
|
+
## Testing
|
|
59
|
+
|
|
60
|
+
When suggesting tests, use Vitest with Angular TestBed:
|
|
61
|
+
|
|
62
|
+
- Co-locate: `*.spec.ts` next to source file
|
|
63
|
+
- Import standalone components in TestBed
|
|
64
|
+
- Mock services with `vi.fn()`
|
|
65
|
+
- Use `HttpTestingController` for HTTP testing
|
|
66
|
+
|
|
67
|
+
## Example Component
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
|
71
|
+
import { EmployeeService } from '../services/employee.service';
|
|
72
|
+
import { Employee } from '../models/employee.model';
|
|
73
|
+
|
|
74
|
+
@Component({
|
|
75
|
+
selector: 'app-employee-list',
|
|
76
|
+
imports: [],
|
|
77
|
+
templateUrl: './employee-list.component.html',
|
|
78
|
+
styleUrl: './employee-list.component.scss',
|
|
79
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
80
|
+
})
|
|
81
|
+
export class EmployeeListComponent {
|
|
82
|
+
private readonly employeeService = inject(EmployeeService);
|
|
83
|
+
|
|
84
|
+
readonly employees = signal<Employee[]>([]);
|
|
85
|
+
readonly loading = signal(false);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Priority
|
|
90
|
+
|
|
91
|
+
If unsure, prefer simpler signal-based standalone code over RxJS-heavy patterns.
|
|
92
|
+
Always match existing project conventions in neighboring files.
|