@c8y/websdk 1023.81.3 → 1023.82.1

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.
Files changed (32) hide show
  1. package/README.md +111 -0
  2. package/dist/ng-add/ai-tools-configure.d.ts +10 -0
  3. package/dist/ng-add/ai-tools-configure.js +318 -0
  4. package/dist/ng-add/ai-tools-configure.js.map +1 -0
  5. package/dist/ng-add/index.js +60 -0
  6. package/dist/ng-add/index.js.map +1 -1
  7. package/dist/ng-add/ng-add.model.d.ts +1 -0
  8. package/dist/templates/ai-configs/claude/CLAUDE.md +98 -0
  9. package/dist/templates/ai-configs/claude/rules/e2e-tests.instructions.md +187 -0
  10. package/dist/templates/ai-configs/claude/rules/frontend.instructions.md +251 -0
  11. package/dist/templates/ai-configs/claude/rules/unit-tests.instructions.md +172 -0
  12. package/dist/templates/ai-configs/cursor/cursor.mdc +98 -0
  13. package/dist/templates/ai-configs/cursor/rules/e2e-tests.instructions.md +187 -0
  14. package/dist/templates/ai-configs/cursor/rules/frontend.instructions.md +251 -0
  15. package/dist/templates/ai-configs/cursor/rules/unit-tests.instructions.md +172 -0
  16. package/dist/templates/ai-configs/gemini/GEMINI.md +98 -0
  17. package/dist/templates/ai-configs/gemini/rules/e2e-tests.instructions.md +181 -0
  18. package/dist/templates/ai-configs/gemini/rules/frontend.instructions.md +247 -0
  19. package/dist/templates/ai-configs/gemini/rules/unit-tests.instructions.md +166 -0
  20. package/dist/templates/ai-configs/github/copilot-instructions.md +98 -0
  21. package/dist/templates/ai-configs/github/instructions/e2e-tests.instructions.md +187 -0
  22. package/dist/templates/ai-configs/github/instructions/frontend.instructions.md +251 -0
  23. package/dist/templates/ai-configs/github/instructions/unit-tests.instructions.md +172 -0
  24. package/dist/templates/ai-configs/jetbrains/guidelines.md +98 -0
  25. package/dist/templates/ai-configs/jetbrains/rules/e2e-tests.instructions.md +181 -0
  26. package/dist/templates/ai-configs/jetbrains/rules/frontend.instructions.md +247 -0
  27. package/dist/templates/ai-configs/jetbrains/rules/unit-tests.instructions.md +166 -0
  28. package/dist/templates/ai-configs/windsurf/guidelines.md +98 -0
  29. package/dist/templates/ai-configs/windsurf/rules/e2e-tests.instructions.md +187 -0
  30. package/dist/templates/ai-configs/windsurf/rules/frontend.instructions.md +251 -0
  31. package/dist/templates/ai-configs/windsurf/rules/unit-tests.instructions.md +172 -0
  32. package/package.json +3 -2
@@ -0,0 +1,181 @@
1
+
2
+ # Cypress Testing Guide
3
+
4
+ ## Test Types and Locations
5
+
6
+ - **E2E tests** — `cypress/e2e/` — run against a real or intercepted Cumulocity tenant
7
+ - **Component tests** — `cypress/component/` — mount Angular components in isolation via `cy.mount()`
8
+ - **Fixtures** — `cypress/fixtures/` — mocked BE responses for component tests
9
+ - **Support** — `cypress/support/` — custom commands (`cy.login()`, `cy.createDevice()`, etc.) and global configuration; auto-imported before every test
10
+ - **Snapshots** — `cypress/snapshots/` — baseline, actual, and diff images for visual regression tests (see Visual Testing)
11
+
12
+ ## E2E Tests
13
+
14
+ ### Authentication and navigation
15
+
16
+ ```typescript
17
+ beforeEach(() => {
18
+ cy.login(Cypress.env('username'), Cypress.env('password'));
19
+ });
20
+
21
+ cy.visit('/apps/cockpit/index.html#/group/123');
22
+ ```
23
+
24
+ Always use `Cypress.env('username')` / `Cypress.env('password')` — never hardcode credentials.
25
+
26
+ ### API interception
27
+
28
+ ```typescript
29
+ cy.intercept('GET', '/inventory/managedObjects/123', mockedObject).as('getMO');
30
+ cy.wait('@getMO');
31
+
32
+ // Query-parameter matching
33
+ cy.intercept(
34
+ { pathname: '/inventory/managedObjects', query: { pageSize: '5' } },
35
+ { managedObjects: [] }
36
+ );
37
+ ```
38
+
39
+ Use `cy.intercept()` to stub slow or unpredictable backend calls; never use fixed `cy.wait(number)`.
40
+
41
+ ### Creating and cleaning up test data
42
+
43
+ Use `cy.request()` for setup. Always clean up in `afterEach`:
44
+
45
+ ```typescript
46
+ afterEach(() => {
47
+ createdIds.forEach(id => {
48
+ cy.request({
49
+ url: `/inventory/managedObjects/${id}?cascade=true`,
50
+ method: 'DELETE',
51
+ failOnStatusCode: false
52
+ });
53
+ });
54
+ });
55
+ ```
56
+
57
+ Use `Cypress._.now()` to generate unique names: `e2eDevice${Cypress._.now()}`.
58
+
59
+ ## Component Tests
60
+
61
+ ### Mounting
62
+
63
+ ```typescript
64
+ cy.mount(MyComponent, {
65
+ imports: [CoreModule.forRoot(), CommonModule, ...],
66
+ providers: [
67
+ { provide: MyService, useValue: stubService }
68
+ ],
69
+ componentProperties: { myInput: value }
70
+ });
71
+ ```
72
+
73
+ All components should be standalone — import them directly, no NgModule wrapping needed.
74
+
75
+ ### Mocked backend with fixtures
76
+
77
+ Component tests that need BE responses can use recorded fixtures:
78
+
79
+ ```typescript
80
+ it('should load data', () => {
81
+ cy.intercept('GET', '/inventory/managedObjects/*', {
82
+ fixture: 'device.json'
83
+ }).as('getDevice');
84
+
85
+ cy.mount(MyComponent, { ... });
86
+ cy.wait('@getDevice');
87
+ cy.get('[data-cy="result"]').should('be.visible');
88
+ });
89
+ ```
90
+
91
+ Store fixture files in `cypress/fixtures/`.
92
+
93
+ ## Selectors
94
+
95
+ Prefer `data-cy` attributes, then role/title attributes. Avoid CSS class selectors unless the class is part of the component's public contract (e.g. icon class names from the design system).
96
+
97
+ Use consistent `data-cy` selectors with pattern: `<component-selector>--<element>-<details>`
98
+
99
+ - Double hyphens (`--`) separate component selector from element
100
+ - Single hyphens connect element type with descriptive details
101
+ - Don't hesitate to use longer names for precision
102
+
103
+ **Example:**
104
+ ```html
105
+ <!-- In component template -->
106
+ <button data-cy="c8y-custom-element-example--reset-button">Reset</button>
107
+ <button data-cy="c8y-custom-element-example--submit">Submit</button>
108
+ ```
109
+
110
+ **Usage:**
111
+ ```typescript
112
+ cy.get('[data-cy="c8y-custom-element-example--reset-button"]').click(); // preferred
113
+ cy.get('[title="Save"]').should('be.visible'); // acceptable for titled elements
114
+ cy.get('c8y-ui-empty-state').should('be.visible'); // OK for component element selectors
115
+ cy.get('.my-layout-class').should(...) // avoid unless class is stable/intentional
116
+ ```
117
+
118
+ ## Assertions
119
+
120
+ ```typescript
121
+ .should('be.visible')
122
+ .should('contain.text', 'expected')
123
+ .should('have.attr', 'href', '#/group/123')
124
+ .should('not.exist')
125
+ .should('be.disabled')
126
+ .should('be.enabled')
127
+ ```
128
+
129
+ Use `.should()` for all assertions (auto-retries). Avoid `.then()` for assertions.
130
+
131
+ ## Helper functions
132
+
133
+ Extract repeated selection and assertion logic into named helper functions at the top of the spec or in a describe-level scope:
134
+
135
+ ```typescript
136
+ function assertBreadcrumb(index: number, text: string) {
137
+ cy.get('[data-cy="breadcrumb-item"]').eq(index).should('contain.text', text);
138
+ }
139
+ ```
140
+
141
+ For component tests with multiple mounting configurations, create a named `mount*` function per variant.
142
+
143
+ ## Visual Testing
144
+
145
+ ```typescript
146
+ cy.compareSnapshot('my-identifier'); // strict
147
+ cy.compareSnapshot('my-identifier', 0.05); // with threshold
148
+ ```
149
+
150
+ Run component tests in the dev container for snapshot consistency with CI:
151
+
152
+ ```bash
153
+ yarn dc:up
154
+ yarn dc:run:spec "cypress/component/path/to/spec.cy.ts"
155
+ yarn dc:base:spec "cypress/component/path/to/spec.cy.ts" # regenerate baseline
156
+ ```
157
+
158
+ Snapshots: `cypress/snapshots/{base,actual,diff}/`
159
+
160
+ To regenerate baseline snapshots, use your project's snapshot regeneration command or manually replace files in `cypress/snapshots/base/`.
161
+
162
+ ## Running Tests
163
+
164
+ ```bash
165
+ # Open Cypress runner
166
+ npx cypress open --config baseUrl=https://<tenant>.cumulocity.com --env username=<u>,password=<p>
167
+
168
+ # Run headless
169
+ npx cypress run --config baseUrl=https://<tenant>.cumulocity.com --e2e --env username=<u>,password=<p> --browser chrome
170
+
171
+ # Filter by title substring (with cypress-grep plugin)
172
+ npx cypress run --env grep=breadcrumb
173
+ ```
174
+
175
+ ## What NOT to Do
176
+
177
+ - No `cy.wait(number)` — use `cy.wait('@alias')` or `.should()` retry
178
+ - No hardcoded tenant IDs, credentials, or device IDs — use `Cypress.env()` (except cases related to e.g. password changes; make sure to document these exceptions clearly in the code and not use credentials of real users)
179
+ - No `it.only` / `describe.only` / `context.only` committed to the repo
180
+ - Use `cy.visit()` for navigation; add custom wait utilities if needed
181
+ - Don't test trivial things just for coverage — test meaningful user-facing behavior
@@ -0,0 +1,247 @@
1
+
2
+ ## Persona
3
+
4
+ You are a senior Angular developer working on a **Cumulocity IoT application**. You leverage the latest Angular features: signals, standalone components, and new control flow syntax. Performance and consistency are paramount.
5
+
6
+ ---
7
+
8
+ ## Resources
9
+
10
+ - Angular docs: https://angular.dev/
11
+ - Angular style guide: https://angular.dev/style-guide
12
+ - Angular signals: https://angular.dev/guide/signals
13
+ - Cumulocity Web SDK / Codex: https://cumulocity.com/codex/
14
+ - Tutorial examples: https://github.com/Cumulocity-IoT/tutorial
15
+
16
+ ---
17
+
18
+ ## Before You Code
19
+
20
+ 1. **Check for existing patterns** — search the codebase for similar components or services before creating new ones. Check `@c8y/ngx-components` exports and [Codex](https://cumulocity.com/codex) (see main instructions for the full mandatory workflow).
21
+
22
+ ---
23
+
24
+ ## Critical Violations
25
+
26
+ These are the highest-severity issues — NEVER introduce them:
27
+
28
+ - **Unmanaged subscriptions** — every `.subscribe()` must have `takeUntilDestroyed()`, `takeUntil()`, or use `async` pipe
29
+ - **Empty catch blocks** — propagate, display via `AlertService.danger()`, or comment why swallowed
30
+ - **Committed secrets** — no API keys, tokens, passwords, or tenant-specific URLs in source
31
+ - **Dynamic gettext()** — `gettext()` must receive a static string literal; dynamic strings break the entire translation pipeline
32
+
33
+ ---
34
+
35
+ ## Components
36
+
37
+ - All new components must be **standalone** — do not set `standalone: true` explicitly (default since Angular 19+)
38
+ - Set `changeDetection: ChangeDetectionStrategy.OnPush` in all `@Component` decorators
39
+ - Keep components small — extract when template exceeds ~150 lines or class exceeds ~200 lines
40
+ - Components focus on presentation; extract business logic, data mapping, and SDK calls to dedicated services
41
+ - Use `readonly` for properties that should not change
42
+ - Use `input()` instead of `@Input`, `output()` instead of `@Output`, `viewChild()` instead of `@ViewChild`
43
+ - Do **not** use `@HostBinding` or `@HostListener` — use the `host` object in decorators
44
+ - Use `NgOptimizedImage` for static images (does not work for inline base64)
45
+ - Prefer Reactive forms over template-driven forms
46
+ - Define model interfaces and constants in **dedicated files** (`models.ts`, `types.ts`), not inside component files
47
+ - Keep JSDoc comments in sync with method signatures — out-of-sync documentation is worse than none
48
+ - Use strict type checking where available per package
49
+ - Split into `.ts`, `.html`, and `.scss` files. Use `c8y` prefix for component selectors
50
+
51
+ ### Example
52
+
53
+ ```ts
54
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
55
+
56
+ @Component({
57
+ selector: 'c8y-example-component',
58
+ templateUrl: './example.component.html',
59
+ changeDetection: ChangeDetectionStrategy.OnPush
60
+ })
61
+ export class ExampleComponent {
62
+ protected readonly isServerRunning = signal(true);
63
+
64
+ toggleServerStatus() {
65
+ this.isServerRunning.update(v => !v);
66
+ }
67
+ }
68
+ ```
69
+
70
+ ```html
71
+ <section class="container">
72
+ @if (isServerRunning()) {
73
+ <span>{{ 'Yes, the server is running' | translate }}</span>
74
+ } @else {
75
+ <span>{{ 'No, the server is not running' | translate }}</span>
76
+ }
77
+ <button (click)="toggleServerStatus()">{{ 'Toggle Server Status' | translate }}</button>
78
+ </section>
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Templates
84
+
85
+ - Use **native control flow** (`@if`, `@for`, `@switch`) — `*ngIf`, `*ngFor`, `*ngSwitch` are **forbidden**
86
+ - Do **not** use `ngClass` or `ngStyle` — use `[class.x]` and `[style.x]` bindings
87
+ - Do **not** import `CoreModule` in standalone components
88
+ - Use `@let` to avoid repeating expressions
89
+ - Use `async` pipe for observables in templates
90
+ - `@for` track: use `item.id` or meaningful property; only `$index` for readonly primitives
91
+ - Keep templates simple — no method calls in bindings (except pipes), no complex logic
92
+ - Do not assume globals like `new Date()` are available in templates
93
+ - Import pipes explicitly when used in a template
94
+ - Use paths relative to the component `.ts` file for external templates/styles
95
+
96
+ ---
97
+
98
+ ## State Management & Subscriptions
99
+
100
+ - **Signals** for local synchronous state; `computed()` for derived state
101
+ - **RxJS/Promises** for async operations and API calls — don't mix paradigms in one use case
102
+ - Do **not** use `mutate` on signals — use `update` or `set`
103
+ - Do **not** run long-lived async (polling, intervals) in components — extract to services
104
+ - Always unsubscribe: `takeUntilDestroyed()`, `takeUntil()`, or `async` pipe — never bare `.unsubscribe()` without `ngOnDestroy`
105
+
106
+ ---
107
+
108
+ ## Dependency Injection & Services
109
+
110
+ - Use **one** DI pattern per file — don't mix constructor injection and `inject()` in the same file
111
+ - `providedIn: 'root'` only for global singletons — scope to components/features when possible
112
+ - Use `@c8y/client` for all Cumulocity REST calls — never `HttpClient` directly
113
+ - Lazy-load feature routes and C8Y widget/plugin modules
114
+
115
+ ---
116
+
117
+ ## Styling
118
+
119
+ - **Always use design tokens** — never hardcode colors (`#hex`, `rgb()`)
120
+ - `var(--brand-primary, var(--c8y-brand-primary))`
121
+ - Common tokens: `--c8y-brand-primary`, `--c8y-root-component-color-*`, `--c8y-root-component-background-*`, `--c8y-root-component-border-color`
122
+ - Prefer Cumulocity utility classes over custom CSS
123
+ - Avoid `::ng-deep` — use component encapsulation or design tokens
124
+ - Don't use inline styles
125
+ - Use SCSS for new files
126
+ - Reference: https://cumulocity.com/codex/design-system/design-tokens/overview
127
+
128
+ ### Utility Class Quick Reference
129
+
130
+ **Spacing:** `m-{side}-{amount}` / `p-{side}-{amount}` — sides: `t`, `r`, `b`, `l` or omit; amounts: 4, 8, 16, 24, 32, 40
131
+
132
+ **Layout:** `d-flex` (row), `d-col` (column) | `j-c-{start|center|end|between|around|evenly}` | `a-i-{start|center|end|stretch}` | `gap-{4|8|16}`
133
+
134
+ **Flex items:** `flex-grow`, `flex-no-shrink`, `flex-auto`, `fit-w`, `fit-h`, `min-width-0`, `min-height-0`
135
+
136
+ **Width/height:** `max-width-100`, `min-width-100`, `max-height-inherit`
137
+
138
+ **Position:** `p-relative`, `p-absolute`, `p-fixed`, `p-sticky`
139
+
140
+ **Text:** `text-left`, `text-center`, `text-right`, `text-pre-wrap`, `text-break-word`, `text-truncate`, `text-truncate-wrap`
141
+
142
+ **Display:** `d-flex`, `d-inline-flex`, `d-col`, `d-block`, `d-inline`, `d-inline-block`, `d-grid`, `d-contents`, `hidden`, `invisible`, `sr-only` | Responsive: `-xs`, `-sm`, `-md`, `-lg`
143
+
144
+ **Icons:** `[c8yIcon]="'icon-name'"` | sizes: `icon-16`, `icon-20`, `icon-32` | decorative: `aria-hidden="true"`
145
+
146
+ ---
147
+
148
+ ## Internationalization
149
+
150
+ - **Templates:** `{{ 'Text' | translate }}` for simple strings
151
+ - **Templates (conditional):** prefer `@let label = 'Text' | translate;` for conditional or repeated translations. `gettext()` in templates is also valid if exposed as a component property and piped through `| translate`
152
+ - **TypeScript:** `translateService.instant(gettext('Text'))` — `gettext()` marks for extraction only
153
+ - **Placeholders:** `{{ 'Result: {{count}}' | translate: { count: value } }}`
154
+ - Every user-visible string must be wrapped
155
+ - Reference: https://cumulocity.com/codex/components/application-and-system/internationalization/overview
156
+
157
+ ---
158
+
159
+ ## Error Handling
160
+
161
+ - **Never leave catch blocks empty** — propagate, display via `AlertService.danger()` with translations, or comment why swallowed
162
+ - API failures must be both logged to console AND displayed to the user
163
+
164
+ ---
165
+
166
+ ## Accessibility
167
+
168
+ WCAG 2.1 Level AA compliance is **mandatory** for all UI work.
169
+
170
+ ### Semantic HTML & Structure
171
+
172
+ - Use semantic elements (`<nav>`, `<main>`, `<section>`, `<article>`, `<aside>`, `<header>`, `<footer>`) — never `<div>` soup
173
+ - Heading levels (`<h1>`–`<h6>`) must follow a logical hierarchy — never skip levels for styling
174
+ - Use `<button>` for actions and `<a>` for navigation — never `<div (click)>` or `<span (click)>`
175
+ - Lists of items must use `<ul>`/`<ol>`/`<li>` — not styled divs
176
+ - Tables must have `<th>` with `scope` attributes; use `<caption>` for table purpose
177
+
178
+ ### Keyboard Operability (2.1.1, 2.1.2, 2.1.4)
179
+
180
+ - All interactive elements must be reachable and operable via keyboard alone
181
+ - Custom interactive elements need `tabindex="0"` and key event handlers (`Enter`, `Space`, `Escape`, arrow keys as appropriate)
182
+ - **No keyboard traps** — focus must always be escapable (modals must return focus to trigger on close)
183
+ - Manage focus programmatically on route changes and after dynamic content insertion
184
+ - Character key shortcuts (single letter) must be remappable, disableable, or only active on focus
185
+
186
+ ### Focus Management
187
+
188
+ - Focus order must follow a logical reading sequence (`tabindex` > 0 is **forbidden**)
189
+ - Focus must be visible at all times — never `outline: none` without a visible replacement
190
+ - Trap focus inside modals/dialogs while open; restore focus to trigger element on close
191
+ - Use `cdkTrapFocus` or `cdkFocusInitial` from `@angular/cdk/a11y` for focus trapping
192
+ - After dynamic content changes (route navigation, drawer open/close), move focus to the new content
193
+
194
+ ### Color & Contrast (1.4.1, 1.4.3, 1.4.11)
195
+
196
+ - **Text contrast:** minimum 4.5:1 for normal text, 3:1 for large text (18px+ or 14px+ bold)
197
+ - **Non-text contrast:** UI components and graphical objects need minimum 3:1 contrast against adjacent colors
198
+ - **Never use color alone** to convey information — always pair with text, icons, or patterns (e.g., error states need icon + color + text, not just red)
199
+ - Use design tokens exclusively — they are pre-validated for contrast compliance, never add custom colors
200
+
201
+ ### Forms & Input (1.3.5, 3.3.1, 3.3.2, 3.3.3, 3.3.4)
202
+
203
+ - Every form control must have a visible `<label>` associated via `for`/`id` — never placeholder-only labels
204
+ - Use `autocomplete` attributes on fields collecting personal data (`name`, `email`, `tel`, `street-address`, etc.)
205
+ - Error messages must: identify the field in error, describe the error, and suggest correction
206
+ - Display errors with `role="alert"` or `aria-live="assertive"` so screen readers announce them
207
+ - Group related controls with `<fieldset>` and `<legend>`
208
+ - Required fields must be marked with `aria-required="true"` and a visible indicator
209
+
210
+ ### Images & Icons (1.1.1)
211
+
212
+ - Informative images: `alt` text describing content or function
213
+ - Decorative images/icons: `aria-hidden="true"` and empty `alt=""`
214
+ - Icon-only buttons must have `aria-label` or visually hidden text: `<button aria-label="{{ 'Delete item' | translate }}"><i [c8yIcon]="'minus-circle'" aria-hidden="true"></i></button>`
215
+ - Complex images (charts, diagrams): provide text alternative via `aria-describedby` pointing to a description
216
+
217
+ ### Dynamic Content & Live Regions (4.1.3)
218
+
219
+ - Status messages (success, info, warnings) must use `role="status"` or `aria-live="polite"`
220
+ - Urgent messages (errors, alerts) must use `role="alert"` or `aria-live="assertive"`
221
+ - Loading states: announce start and end — e.g., `aria-busy="true"` on the container, announce completion
222
+ - Content that updates without page reload must notify assistive technology
223
+ - Never auto-update content faster than the user can read it; provide pause/stop controls for auto-rotating content
224
+
225
+ ### Text & Content (1.4.4, 1.4.10, 1.4.12, 1.4.13)
226
+
227
+ - Text must be resizable up to 200% without loss of content or functionality
228
+ - Content must reflow at 320px viewport width without horizontal scrolling
229
+ - No loss of content when users override text spacing (line height 1.5x, letter spacing 0.12em, word spacing 0.16em)
230
+ - Content revealed on hover/focus (tooltips) must be: dismissible (Esc), hoverable (mouse can reach it), and persistent (stays until dismissed)
231
+
232
+ ### ARIA Usage
233
+
234
+ - **First rule of ARIA:** don't use ARIA if a native HTML element provides the semantics (`<button>` over `<div role="button">`)
235
+ - Use `aria-label` or `aria-labelledby` for elements without visible text labels
236
+ - Use `aria-describedby` for supplementary descriptions (help text, constraints)
237
+ - Use `aria-expanded`, `aria-controls`, `aria-haspopup` for disclosure widgets
238
+ - Custom widgets (tabs, trees, comboboxes) must implement the full [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) keyboard and role pattern
239
+ - Use `aria-current="page"` for active navigation links
240
+
241
+ ### Testing Checklist
242
+
243
+ - **Keyboard-only:** tab through entire page, operate every control, escape every modal
244
+ - **Screen reader:** test critical flows with VoiceOver (macOS) — ensure all content is announced
245
+ - **Zoom:** verify layout at 200% zoom and 320px viewport
246
+ - **Color:** inspect with simulated color-blindness (DevTools → Rendering → Emulate vision deficiencies)
247
+ - **Automated:** run `axe-core` or Lighthouse accessibility audit — zero violations is the baseline, not the goal
@@ -0,0 +1,166 @@
1
+
2
+ # Unit Testing Guide
3
+
4
+ ## Overview
5
+ Unit tests use Jest with TestBed for Angular component setup and jest.fn() for mocking services.
6
+
7
+ # Common
8
+
9
+ ## Test File Location
10
+ - Unit test files are co-located with the code they test
11
+ - Use `.spec.ts` extension for test files
12
+ - Example: `my-component.ts` → `my-component.spec.ts`
13
+
14
+ ## Basic Test Structure
15
+ ```typescript
16
+ describe('ComponentName', () => {
17
+ let component: ComponentName;
18
+ let fixture: ComponentFixture<ComponentName>;
19
+
20
+ beforeEach(async () => {
21
+ await TestBed.configureTestingModule({
22
+ imports: [ComponentName],
23
+ providers: [/* services */]
24
+ }).compileComponents();
25
+ fixture = TestBed.createComponent(ComponentName);
26
+ component = fixture.componentInstance;
27
+ });
28
+
29
+ test('should do something specific', () => {
30
+ fixture.detectChanges();
31
+ expect(actual).toBe(expected);
32
+ });
33
+ });
34
+ ```
35
+
36
+ **Key points:**
37
+ - Always use `async` on `beforeEach` and `await` + `.compileComponents()` on `configureTestingModule()` to properly compile components
38
+ - Always call `fixture.detectChanges()` before asserting anything about the component (triggers Angular initialization and change detection)
39
+
40
+ ## Angular Testing
41
+ - Test component behavior, not implementation details
42
+ - Test DOM interactions through the fixture using `fixture.detectChanges()`
43
+ - For async operations: use `fixture.whenStable()` to wait for pending async tasks or `async`/`waitForAsync` and `fakeAsync`
44
+
45
+ ## Best Practices
46
+ - **Descriptive test names**: Use "should..." pattern in it() names
47
+ - **Be concise**: Keep tests to as few lines as possible. Avoid unnecessary variable declarations. Use helper functions for common test data.
48
+ - **Structure tests logically**: Group related tests using nested `describe` blocks
49
+ - **Mock external dependencies**: Don't test third-party code
50
+ - **Test user behavior**: Focus on what users experience, not implementation details
51
+ - **Avoid arbitrary sleeps**: Do NOT use `setTimeout` in tests — instead use `fixture.whenStable()` or `await` for async operations
52
+ - **Don't duplicate**: Use `beforeEach` for common setup, or extract helper functions
53
+ - **Avoid brittleness**: Don't depend on exact backend data structure details that may change
54
+ - **Always call `detectChanges()` before assertions**: Angular change detection must run before testing component state or DOM
55
+ - **Mock comprehensively**: If a service has 10 public methods and your component calls 8 of them, mock all 8 in the test setup to avoid runtime errors later
56
+
57
+ ## Common Pitfalls to Avoid
58
+ - ❌ **Forgetting `async` and `.compileComponents()`**: `beforeEach(() => { ... })` without `async`/`await` leaves components uncompiled
59
+ - ❌ **Missing `fixture.detectChanges()`**: Component won't initialize without this; properties will be undefined
60
+ - ❌ **Incomplete service mocks**: Mocking only some methods causes "is not a function" errors on unmocked methods
61
+ - ❌ **Wrong mock return types**: Returning `[]` when the method returns `{ data: [], paging: {} }` causes destructuring errors
62
+ - ❌ **Not providing all dependencies**: Missing providers (e.g., `TranslateService`) causes "No provider found" errors
63
+ - ✅ **DO**: Mock all methods, use correct async patterns, always call `detectChanges()`, verify return types match the real service
64
+
65
+ ## Common Test Patterns
66
+ ```typescript
67
+ // Testing component initialization
68
+ it('should initialize with default values', () => {
69
+ fixture.detectChanges();
70
+ expect(component.propertyName).toBe(expectedValue);
71
+ });
72
+
73
+ // Testing async operations
74
+ it('should load data on init', async () => {
75
+ fixture.detectChanges();
76
+ await fixture.whenStable();
77
+ fixture.detectChanges();
78
+ expect(component.data).toBeDefined();
79
+ });
80
+
81
+ // Testing user interactions
82
+ it('should emit event when button clicked', () => {
83
+ fixture.detectChanges();
84
+ jest.spyOn(component.outputEvent, 'emit');
85
+ fixture.nativeElement.querySelector('button').click();
86
+ expect(component.outputEvent.emit).toHaveBeenCalled();
87
+ });
88
+ ```
89
+
90
+ ## TypeScript in Tests
91
+ - Use proper types, avoid `any`
92
+ - Type your test data and mocks
93
+ - Use interfaces from the application code
94
+ - Enable strict mode for better type safety
95
+
96
+ # Setup & Mocking
97
+
98
+ ## Mocking
99
+
100
+ ### TestBed Setup with jest.fn()
101
+
102
+ Use **TestBed.configureTestingModule** for component setup and **jest.fn()** for mock service objects:
103
+
104
+ ```typescript
105
+ beforeEach(async () => {
106
+ await TestBed.configureTestingModule({
107
+ imports: [MyComponent],
108
+ providers: [
109
+ {
110
+ provide: MyService,
111
+ useValue: {
112
+ getData: jest.fn().mockResolvedValue(data),
113
+ logout: jest.fn()
114
+ }
115
+ }
116
+ ]
117
+ }).compileComponents();
118
+ fixture = TestBed.createComponent(MyComponent);
119
+ component = fixture.componentInstance;
120
+ });
121
+ ```
122
+
123
+ **Important mocking guidelines:**
124
+ - **Mock all public methods** that the component or its children might call, not just a few
125
+ - **Match return types exactly**: If a method returns `Promise<T>`, use `.mockResolvedValue(T)`; if it returns an object with properties, return a properly structured object (e.g., `{ data: [], paging: {} }`)
126
+ - **Use `.mockResolvedValue()` for async methods** to return promises correctly
127
+ - **Use `.mockReturnValue()` for synchronous methods**
128
+ - **Type your mocks** to catch missing methods early: `useValue: {...} as jest.Mocked<MyService>`
129
+
130
+ ### Spying on Component/Service Methods
131
+
132
+ ```typescript
133
+ const service = TestBed.inject(MyService);
134
+ jest.spyOn(service, 'getData').mockReturnValue(value);
135
+
136
+ // Or for component methods:
137
+ jest.spyOn(component, 'onSubmit');
138
+ component.handleForm();
139
+ expect(component.onSubmit).toHaveBeenCalled();
140
+ ```
141
+
142
+ ## Matchers
143
+ - `toBe()` - Strict equality (===)
144
+ - `toEqual()` - Deep equality
145
+ - `toBeTruthy()` / `toBeFalsy()`
146
+ - `toContain()` - Array/string contains
147
+ - `toHaveBeenCalled()` - Spy was called
148
+ - `toHaveBeenCalledWith(args)` - Spy called with specific arguments
149
+ - `toHaveLength(n)` - Array/string length
150
+ - `toThrow()` / `toThrowError()` - Exception testing
151
+ - See [Jest matchers documentation](https://jestjs.io/docs/expect) for complete list
152
+
153
+ ## Running Tests
154
+ - Run specific file: `yarn jest my-component.spec.ts`
155
+ - Run with watch mode: `yarn jest --watch my-component.spec.ts`
156
+ - Run single test suite: `yarn jest my-component.spec.ts -t '^SuiteName(\\s.*)?$'`
157
+ - Debug race conditions: `yarn jest my-component.spec.ts --testNamePattern="Test Name" --maxWorkers=1`
158
+
159
+ ## Coverage
160
+ - Aim for high coverage but focus on meaningful tests
161
+ - Ensure exception cases are covered where possible
162
+ - Focus on critical paths and edge cases
163
+
164
+ ## Resources
165
+ - Jest documentation: https://jestjs.io/
166
+ - Angular testing guide: https://angular.dev/guide/testing