@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,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
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
## Project Identity
|
|
2
|
+
|
|
3
|
+
**Cumulocity IoT Web Application** — custom web frontend built with the Cumulocity Web SDK. Extends the Cumulocity IoT platform with custom features and integrations.
|
|
4
|
+
|
|
5
|
+
- **Stack:** Angular 20, TypeScript 5.9.3, RxJS 7.8, Jest 30, Cypress 15, ESLint (airbnb-base + angular-eslint)
|
|
6
|
+
- **Core Libraries:** `@c8y/ngx-components`, `@c8y/client`, `@c8y/toolkit`, `@c8y/devkit`, `@c8y/bootstrap`
|
|
7
|
+
- **Structure:** Standard Angular CLI application scaffolded with `ng add @c8y/websdk`
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🛑 MANDATORY WORKFLOW: UI Planning & Implementation
|
|
12
|
+
|
|
13
|
+
**STOP: Before analyzing, planning, designing, OR implementing ANY UI feature/component, you MUST complete this workflow. No exceptions.**
|
|
14
|
+
|
|
15
|
+
### Step 1: Fetch Codex Documentation (REQUIRED BEFORE PLANNING)
|
|
16
|
+
|
|
17
|
+
When the user requests ANY UI work (planning, design, implementation), your **FIRST ACTION** must be:
|
|
18
|
+
|
|
19
|
+
1. **Fetch** https://cumulocity.com/codex/llms.txt
|
|
20
|
+
2. **Search** the content for relevant component keywords (modal, button, form, table, etc.)
|
|
21
|
+
3. **If found:** Read the specific `.md` documentation file like `https://cumulocity.com/codex/components/forms/editor.md`
|
|
22
|
+
4. **Read and analyze** the documentation content
|
|
23
|
+
5. **Locate examples** in the public tutorial repository:
|
|
24
|
+
e.g. for file path './packages/tutorial/src/selector/asset-selector-example/tree-options/asset-selector-tree-example.component.ts' get file from
|
|
25
|
+
'https://github.com/Cumulocity-IoT/tutorial/tree/main/src/selector/asset-selector-example/tree-options/asset-selector-tree-example.component.ts'
|
|
26
|
+
6. **Read example code** to understand the official pattern
|
|
27
|
+
|
|
28
|
+
**⛔ INVALID:**
|
|
29
|
+
- ❌ "I'll list the Codex URL as a resource to review later"
|
|
30
|
+
- ❌ "The plan should include fetching the documentation"
|
|
31
|
+
- ❌ "Before implementation, we need to review the Codex"
|
|
32
|
+
- ❌ Making a plan without actually fetching and reading documentation
|
|
33
|
+
|
|
34
|
+
**✅ VALID:**
|
|
35
|
+
- ✓ Fetch llms.txt → Find modal docs → Read content → Find tutorial examples → Read example code → Then create plan
|
|
36
|
+
|
|
37
|
+
### Step 2: Check for Existing Components
|
|
38
|
+
|
|
39
|
+
After reviewing documentation:
|
|
40
|
+
|
|
41
|
+
1. Search `@c8y/ngx-components` for existing implementations
|
|
42
|
+
2. Use a matching Cumulocity component if one exists — **never build custom equivalents**
|
|
43
|
+
3. Only proceed with custom solution if nothing suitable exists
|
|
44
|
+
|
|
45
|
+
### Step 3: Plan or Implement
|
|
46
|
+
|
|
47
|
+
Only after completing Steps 1-2, proceed with planning or implementation.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Universal Rules — TypeScript
|
|
52
|
+
|
|
53
|
+
- Target is ES2022; do not use syntax unavailable in ES2022
|
|
54
|
+
- Enable strict mode where possible; use explicit types or `unknown` with narrowing guards
|
|
55
|
+
- Avoid `any`; prefer explicit types
|
|
56
|
+
- Use path aliases for imports: `@c8y/ngx-components`, `@c8y/client`, `@c8y/devkit`, `@c8y/options`
|
|
57
|
+
- `experimentalDecorators: true` is set globally; Angular decorator syntax is valid as-is
|
|
58
|
+
|
|
59
|
+
## Universal Rules — Cumulocity Platform
|
|
60
|
+
|
|
61
|
+
### Other rules
|
|
62
|
+
|
|
63
|
+
- Use `@c8y/client` services for all Cumulocity REST calls — do **not** use `HttpClient` directly for platform endpoints
|
|
64
|
+
- Check `@c8y/ngx-components` before building custom UI components — it exports a large shared library. Search by visual functionality in [Codex](https://cumulocity.com/codex)
|
|
65
|
+
- Wrap every user-facing string with `C8yTranslatePipe` (`| translate`) or `TranslateService`:
|
|
66
|
+
- ✅ `{{ 'Save' | translate }}` ❌ `Save`
|
|
67
|
+
|
|
68
|
+
## Angular Patterns
|
|
69
|
+
|
|
70
|
+
- All new components must be **standalone** (`standalone: true`) — do not create NgModule-based components
|
|
71
|
+
- Prefer `ChangeDetectionStrategy.OnPush` for new components (no project-wide enforcement, but scale demands it)
|
|
72
|
+
- Constructor injection and `inject()` function are both present; either is acceptable
|
|
73
|
+
- Manage subscriptions with `async` pipe or `takeUntilDestroyed()` — avoid bare `unsubscribe()` without `ngOnDestroy`
|
|
74
|
+
- Use signals for local component state
|
|
75
|
+
- Consider extracting child components when template exceeds ~150 lines or class exceeds ~200 lines
|
|
76
|
+
|
|
77
|
+
## Code Quality (PR Review Focus)
|
|
78
|
+
|
|
79
|
+
- Remove `console.log` / `console.debug` before commit; `console.error` acceptable in caught exceptions
|
|
80
|
+
- Flag methods exceeding ~40 lines — consider decomposition
|
|
81
|
+
- Remove commented-out code; use git history for recovery
|
|
82
|
+
- New features should have `*.spec.ts` unit tests; new user flows should have Cypress coverage
|
|
83
|
+
- Document types, methods and properties that are not self-explanatory; don't explain ones that are obvious
|
|
84
|
+
- Test use cases that are truly worth testing, do not create test cases just for the sake of tests volume or coverate
|
|
85
|
+
|
|
86
|
+
## Security Checks
|
|
87
|
+
|
|
88
|
+
- Flag hardcoded tenant IDs, device IDs, credentials, or API tokens — use `Cypress.env()` in tests, env config in production
|
|
89
|
+
- Review components rendering dynamic device/asset data for XSS: prefer `{{ }}` over `[innerHTML]` with unescaped values
|
|
90
|
+
- Treat IoT payload data as untrusted — validate and sanitize before display
|
|
91
|
+
- Do not expose internal hostnames or credentials in client-side code
|
|
92
|
+
|
|
93
|
+
## What NOT to Flag
|
|
94
|
+
|
|
95
|
+
- **Import order** — managed by ESLint
|
|
96
|
+
- **Quote style / semicolons** — ESLint enforces single quotes and required semicolons
|
|
97
|
+
- **Trailing commas on function parameters** — ESLint explicitly disallows them
|
|
98
|
+
- **`skipLibCheck: true`** in tsconfig — intentional project setting
|
|
@@ -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
|