@cognite/dune 0.3.5 → 0.3.7
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>eslint.config.mjs'
|
|
3
3
|
---
|
|
4
|
+
import { auraEslintPlugin } from '@cognite/aura/eslint';
|
|
4
5
|
import js from '@eslint/js';
|
|
5
6
|
import importPlugin from 'eslint-plugin-import';
|
|
6
7
|
import noOnlyTestsPlugin from 'eslint-plugin-no-only-tests';
|
|
@@ -80,6 +81,7 @@ export default tseslint.config(
|
|
|
80
81
|
},
|
|
81
82
|
},
|
|
82
83
|
plugins: {
|
|
84
|
+
aura: auraEslintPlugin,
|
|
83
85
|
'react-hooks': reactHooks,
|
|
84
86
|
'react-refresh': reactRefresh,
|
|
85
87
|
},
|
|
@@ -89,6 +91,7 @@ export default tseslint.config(
|
|
|
89
91
|
'@typescript-eslint/no-explicit-any': 'error',
|
|
90
92
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
91
93
|
'@typescript-eslint/no-unused-vars': ['error', noUnusedVarsOptions],
|
|
94
|
+
'aura/no-overriding-styles': 'warn',
|
|
92
95
|
'no-unused-vars': 'off',
|
|
93
96
|
'react-refresh/only-export-components': ['error', { allowConstantExport: true }],
|
|
94
97
|
},
|
|
@@ -7,7 +7,8 @@ to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>AGENTS.md'
|
|
|
7
7
|
|
|
8
8
|
Inject dependencies via React context (hooks/components) or factory-override pattern (plain functions). Never hard-code dependencies.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
### React context
|
|
11
|
+
|
|
11
12
|
```typescript
|
|
12
13
|
const defaultDeps = { useDataSource, useAnalytics };
|
|
13
14
|
export type MyHookContextType = typeof defaultDeps;
|
|
@@ -18,7 +19,8 @@ export function useMyHook() {
|
|
|
18
19
|
}
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
### Factory overrides
|
|
23
|
+
|
|
22
24
|
```typescript
|
|
23
25
|
type Deps = { serviceFactory: () => SomeService };
|
|
24
26
|
const defaultDeps: Deps = { serviceFactory: () => new SomeServiceImpl() };
|
|
@@ -40,7 +42,9 @@ export interface DataService {
|
|
|
40
42
|
save(data: Data): Promise<void>;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
export class ApiDataService implements DataService {
|
|
45
|
+
export class ApiDataService implements DataService {
|
|
46
|
+
/* ... */
|
|
47
|
+
}
|
|
44
48
|
```
|
|
45
49
|
|
|
46
50
|
---
|
|
@@ -53,51 +57,89 @@ Business logic lives in `use<Name>ViewModel`. Components only render.
|
|
|
53
57
|
export function useTodoViewModel(): TodoViewModel {
|
|
54
58
|
const { useTodoStorage, addTodoCommand } = useContext(TodoViewModelContext);
|
|
55
59
|
const storage = useTodoStorage();
|
|
56
|
-
const addTodo = useCallback(
|
|
60
|
+
const addTodo = useCallback(
|
|
61
|
+
(text: string) => addTodoCommand(text, storage),
|
|
62
|
+
[storage, addTodoCommand]
|
|
63
|
+
);
|
|
57
64
|
return { todos: storage.listAllTodos(), addTodo };
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
export const TodoView = () => {
|
|
61
68
|
const { todos, addTodo } = useTodoViewModel();
|
|
62
|
-
return <ul>{todos.map(t => <TodoItem key={t.id} todo={t} onAdd={addTodo} />)}</ul>;
|
|
69
|
+
return <ul>{todos.map((t) => <TodoItem key={t.id} todo={t} onAdd={addTodo} />)}</ul>;
|
|
63
70
|
};
|
|
64
71
|
```
|
|
65
72
|
|
|
66
73
|
---
|
|
67
74
|
|
|
68
|
-
## 4. Test-
|
|
75
|
+
## 4. Test-First Development
|
|
76
|
+
|
|
77
|
+
Write tests before implementation for all non-trivial behavior changes.
|
|
69
78
|
|
|
70
|
-
###
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
### Preferred order
|
|
80
|
+
|
|
81
|
+
Start with behavior-focused tests so requirements are specified before implementation details:
|
|
82
|
+
|
|
83
|
+
1. Integration tests (user-visible behavior)
|
|
84
|
+
2. Unit tests (isolated module logic)
|
|
73
85
|
3. Source files to make tests pass
|
|
74
86
|
|
|
87
|
+
For bug fixes, start by adding a failing regression test that reproduces the issue.
|
|
88
|
+
|
|
89
|
+
Every new module with logic (service, hook, component, utility) must include a corresponding `*.test.ts(x)` file in the same changeset.
|
|
90
|
+
|
|
91
|
+
### Reasonable exceptions
|
|
92
|
+
|
|
93
|
+
- Bootstrapping/entry files (for example `main.tsx`)
|
|
94
|
+
- Generated code
|
|
95
|
+
- Trivial pure-markup components with no logic or state
|
|
96
|
+
|
|
97
|
+
### Test levels in this repo
|
|
98
|
+
|
|
99
|
+
- **Integration test**: validates behavior across boundaries (for example component + view model + service contract), mocking only external systems such as network APIs.
|
|
100
|
+
- **Unit test**: validates one module in isolation (service, hook, utility, or component behavior).
|
|
101
|
+
|
|
102
|
+
### Minimum expected coverage by file type
|
|
103
|
+
|
|
104
|
+
| File type | Required test cases |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| Service (`*Service.ts`) | Correct request construction; response parsing; error thrown on non-OK status |
|
|
107
|
+
| ViewModel hook (`use*ViewModel.ts`) | Loading state; success state with correct derived values; error state |
|
|
108
|
+
| Pure utility / helper | Every exported function and all meaningful branches |
|
|
109
|
+
| View component | Renders expected content from props; loading/error/empty states where applicable |
|
|
110
|
+
|
|
75
111
|
### Conventions
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
112
|
+
|
|
113
|
+
- Files: `*.test.ts(x)`; runner: **Vitest** (`pnpm test` or `vitest run`)
|
|
114
|
+
- Structure: Arrange / Act / Assert (add explicit comments for longer tests)
|
|
115
|
+
- One behavior per test
|
|
116
|
+
- Keep helper functions at the bottom of the file
|
|
117
|
+
- Prefer dependency/context injection over `vi.mock`; add a short reason when `vi.mock` is unavoidable
|
|
80
118
|
|
|
81
119
|
### Type-safe mocks
|
|
120
|
+
|
|
82
121
|
```typescript
|
|
83
122
|
// Preferred: vi.fn(() => ...) for consistent behavior
|
|
84
123
|
mockContext = { useUserInfo: vi.fn(() => ({ data: mockUser, isFetched: true })) };
|
|
85
124
|
|
|
86
|
-
//
|
|
125
|
+
// Per-test reconfiguration
|
|
87
126
|
mockContext = { useUserInfo: vi.fn() };
|
|
88
127
|
vi.mocked(mockContext.useUserInfo).mockReturnValue({ data: undefined, isFetched: true });
|
|
89
128
|
```
|
|
90
129
|
|
|
91
|
-
For full interface mocks, use `assert.fail` on methods the unit under test should never call
|
|
130
|
+
For full interface mocks, use `assert.fail` on methods the unit under test should never call, or preferably define a narrower interface.
|
|
92
131
|
|
|
93
132
|
```typescript
|
|
94
133
|
mockStorage = {
|
|
95
134
|
list: vi.fn(),
|
|
96
|
-
retrieve: vi.fn(() => {
|
|
135
|
+
retrieve: vi.fn(() => {
|
|
136
|
+
assert.fail('Not implemented');
|
|
137
|
+
}),
|
|
97
138
|
};
|
|
98
139
|
```
|
|
99
140
|
|
|
100
|
-
### React
|
|
141
|
+
### React hook test pattern
|
|
142
|
+
|
|
101
143
|
```typescript
|
|
102
144
|
describe(useMyHook.name, () => {
|
|
103
145
|
let mockContext: MyContextType;
|
|
@@ -112,25 +154,29 @@ describe(useMyHook.name, () => {
|
|
|
112
154
|
|
|
113
155
|
it('should ...', async () => {
|
|
114
156
|
const { result } = renderHook(() => useMyHook(), { wrapper });
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
157
|
+
await act(async () => {
|
|
158
|
+
await result.current.someAction();
|
|
159
|
+
});
|
|
118
160
|
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
119
161
|
});
|
|
120
162
|
});
|
|
121
163
|
```
|
|
122
164
|
|
|
123
|
-
###
|
|
124
|
-
|
|
125
|
-
|
|
165
|
+
### Shared mock data
|
|
166
|
+
|
|
167
|
+
Place reusable factories in `src/__mocks__/`. Use `.test` TLD for fake URLs (RFC 2606).
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 5. TypeScript Rules
|
|
172
|
+
|
|
173
|
+
- Never use `any`; prefer `unknown` or explicit strong types
|
|
174
|
+
- Never use `as unknown as T`; for partial test doubles use `{ ...defaults, ...overrides } as T`
|
|
175
|
+
- Use direct React type imports: `import type { ComponentType, ReactNode } from 'react'`
|
|
126
176
|
|
|
127
177
|
```typescript
|
|
128
178
|
function createMockWindow(overrides: Partial<Window> = {}): Window {
|
|
129
179
|
return { postMessage: vi.fn(), ...overrides } as Window;
|
|
130
180
|
}
|
|
131
181
|
```
|
|
132
|
-
|
|
133
|
-
- Use direct React type imports: `import type { ComponentType, ReactNode } from 'react'`
|
|
134
|
-
|
|
135
|
-
### Shared mock data
|
|
136
|
-
Place reusable factories in `src/__mocks__/`. Use `.test` TLD for fake URLs (RFC 2606).
|
|
182
|
+
---
|
|
@@ -21,9 +21,9 @@ to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>package.json'
|
|
|
21
21
|
"deploy-preview": "dune deploy:interactive"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@cognite/aura": "^0.1.
|
|
24
|
+
"@cognite/aura": "^0.1.5",
|
|
25
25
|
"@cognite/sdk": "^10.3.0",
|
|
26
|
-
"@cognite/dune": "^0.3.
|
|
26
|
+
"@cognite/dune": "^0.3.7",
|
|
27
27
|
"@tabler/icons-react": "^3.35.0",
|
|
28
28
|
"@tanstack/react-query": "^5.90.10",
|
|
29
29
|
"clsx": "^2.1.1",
|
|
@@ -92,8 +92,8 @@ function App() {
|
|
|
92
92
|
<Card>
|
|
93
93
|
<div className="p-15 gap-16">
|
|
94
94
|
<CardHeader>
|
|
95
|
-
<CardTitle as="h1"
|
|
96
|
-
<CardDescription
|
|
95
|
+
<CardTitle as="h1">Welcome to Dune</CardTitle>
|
|
96
|
+
<CardDescription>{INTRO_COPY}</CardDescription>
|
|
97
97
|
</CardHeader>
|
|
98
98
|
|
|
99
99
|
<CardContent>
|
|
@@ -119,34 +119,36 @@ function App() {
|
|
|
119
119
|
</span>
|
|
120
120
|
</div>
|
|
121
121
|
</CollapsibleTrigger>
|
|
122
|
-
<CollapsibleContent
|
|
123
|
-
{step.body}
|
|
122
|
+
<CollapsibleContent>
|
|
123
|
+
<div className="py-2">{step.body}</div>
|
|
124
124
|
</CollapsibleContent>
|
|
125
125
|
</Collapsible>
|
|
126
126
|
))}
|
|
127
127
|
</div>
|
|
128
128
|
|
|
129
|
-
<
|
|
130
|
-
<
|
|
131
|
-
<
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
129
|
+
<div className="mb-10">
|
|
130
|
+
<Alert variant="secondary">
|
|
131
|
+
<AlertDescription>
|
|
132
|
+
<div className="flex flex-wrap items-center gap-2 text-lg">
|
|
133
|
+
<span>Your app will deploy to</span>
|
|
134
|
+
{orgLabel ? (
|
|
135
|
+
<>
|
|
136
|
+
<span>org</span>
|
|
137
|
+
<Badge variant="nordic" background>
|
|
138
|
+
{orgLabel}
|
|
139
|
+
</Badge>
|
|
140
|
+
<span>and project</span>
|
|
141
|
+
</>
|
|
142
|
+
) : (
|
|
143
|
+
<span>project</span>
|
|
144
|
+
)}
|
|
145
|
+
<Badge variant="nordic" background>
|
|
146
|
+
{projectLabel}
|
|
147
|
+
</Badge>
|
|
148
|
+
</div>
|
|
149
|
+
</AlertDescription>
|
|
150
|
+
</Alert>
|
|
151
|
+
</div>
|
|
150
152
|
|
|
151
153
|
<Collapsible>
|
|
152
154
|
<CollapsibleTrigger className="w-full">
|
|
@@ -160,20 +162,22 @@ function App() {
|
|
|
160
162
|
</span>
|
|
161
163
|
</div>
|
|
162
164
|
</CollapsibleTrigger>
|
|
163
|
-
<CollapsibleContent
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
165
|
+
<CollapsibleContent>
|
|
166
|
+
<div className="py-2">
|
|
167
|
+
<p>
|
|
168
|
+
For additional support and feedback, please head to{' '}
|
|
169
|
+
<a
|
|
170
|
+
href={DUNE_DOCUMENTATION_HREF}
|
|
171
|
+
rel="noreferrer"
|
|
172
|
+
style={{ color: '#486AED' }}
|
|
173
|
+
target="_blank"
|
|
174
|
+
>
|
|
175
|
+
Dune documentation
|
|
176
|
+
</a>{' '}
|
|
177
|
+
or the{' '}
|
|
178
|
+
<span style={{ color: '#486AED' }}>#Dune Slack channel</span>.
|
|
179
|
+
</p>
|
|
180
|
+
</div>
|
|
177
181
|
</CollapsibleContent>
|
|
178
182
|
</Collapsible>
|
|
179
183
|
</div>
|
package/package.json
CHANGED