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