svelte-declarative-testing 0.1.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/.prettierrc +16 -0
- package/README.md +270 -0
- package/package.json +61 -0
- package/src/components/core/Check.svelte +11 -0
- package/src/components/core/Describe.svelte +37 -0
- package/src/components/core/Test.svelte +56 -0
- package/src/components/core/Wrapper.svelte +8 -0
- package/src/components/core/context.ts +7 -0
- package/src/components/core/index.d.ts +39 -0
- package/src/components/core/index.js +3 -0
- package/src/components/testing-library/Check.svelte +9 -0
- package/src/components/testing-library/Test.svelte +11 -0
- package/src/components/testing-library/index.d.ts +14 -0
- package/src/components/testing-library/index.js +3 -0
- package/src/components/vitest-browser-svelte/Check.svelte +9 -0
- package/src/components/vitest-browser-svelte/Test.svelte +11 -0
- package/src/components/vitest-browser-svelte/index.d.ts +14 -0
- package/src/components/vitest-browser-svelte/index.js +3 -0
- package/src/index.js +2 -0
- package/src/plugins/index.d.ts +3 -0
- package/src/plugins/vitest.js +93 -0
package/.prettierrc
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"useTabs": false,
|
|
3
|
+
"singleQuote": true,
|
|
4
|
+
"trailingComma": "all",
|
|
5
|
+
"htmlWhitespaceSensitivity": "ignore",
|
|
6
|
+
"printWidth": 100,
|
|
7
|
+
"plugins": ["prettier-plugin-svelte"],
|
|
8
|
+
"overrides": [
|
|
9
|
+
{
|
|
10
|
+
"files": "*.svelte",
|
|
11
|
+
"options": {
|
|
12
|
+
"parser": "svelte"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# svelte-declarative-testing
|
|
2
|
+
|
|
3
|
+
One of the great things about Svelte is its compiler, producing a minimal
|
|
4
|
+
runtime and providing a great, declarative way to build components.
|
|
5
|
+
Unfortunately, unlike many other JavaScript frameworks, which use jsx or tagged
|
|
6
|
+
template functions to render components in a declarative manner inside
|
|
7
|
+
JavaScript files, Svelte has no such feature. Consider the following structure
|
|
8
|
+
of composed components:
|
|
9
|
+
|
|
10
|
+
```svelte
|
|
11
|
+
<Popover>
|
|
12
|
+
<PopoverTrigger>Open Menu</PopoverTrigger>
|
|
13
|
+
<PopoverMenu>
|
|
14
|
+
<PopoverMenuItem href="/account">Account</PopoverMenuItem>
|
|
15
|
+
<PopoverMenuItem href="/logout">Log out</PopoverMenuItem>
|
|
16
|
+
</PopoverMenu>
|
|
17
|
+
</Popover>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
There _are_ existing ways to render this in your tests, but none of them are
|
|
21
|
+
ergonomic. You can:
|
|
22
|
+
|
|
23
|
+
- create a separate `.svelte` file for each configuration of your composed
|
|
24
|
+
components you want to test and pass that component to `render()`; or
|
|
25
|
+
- create a single `.svelte` file and wrap all of your configurations in
|
|
26
|
+
`{#snippet <name>()}...{/snippet}`, along with a `{@render children()}`. You
|
|
27
|
+
export all the snippets and import them along with the default export, pass
|
|
28
|
+
the default export to `render(...)` and the snippet to the `children`
|
|
29
|
+
property; or
|
|
30
|
+
- Use something like [vite-plugin-svelte-inline-component][vpsic] which allows
|
|
31
|
+
you to use tagged template literals along with some directive comments in your
|
|
32
|
+
test files.
|
|
33
|
+
|
|
34
|
+
[vpsic]: https://github.com/hanielu/vite-plugin-svelte-inline-component
|
|
35
|
+
|
|
36
|
+
Using this library, you can write your tests in Svelte syntax. This gives you
|
|
37
|
+
much greater ergonomics and flexibility, especially for the above composition.
|
|
38
|
+
Your tests can look like this:
|
|
39
|
+
|
|
40
|
+
```svelte
|
|
41
|
+
<!-- Popover.test.svelte -->
|
|
42
|
+
<script>
|
|
43
|
+
import { Test, Check } from 'svelte-declarative-testing/vitest-browser-svelte';
|
|
44
|
+
import { Popover, PopoverTrigger, PopoverMenu, PopoverMenuItem } from './';
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<Test it="opens the menu when the trigger is clicked">
|
|
48
|
+
<!-- children of the Test are rendered automatically -->
|
|
49
|
+
<Popover>
|
|
50
|
+
<PopoverTrigger>Open Menu</PopoverTrigger>
|
|
51
|
+
<PopoverMenu>
|
|
52
|
+
<PopoverMenuItem href="/account">Account</PopoverMenuItem>
|
|
53
|
+
<PopoverMenuItem href="/logout">Log out</PopoverMenuItem>
|
|
54
|
+
</PopoverMenu>
|
|
55
|
+
</Popover>
|
|
56
|
+
|
|
57
|
+
<!-- one or more test functions can be provided as checks -->
|
|
58
|
+
{#snippet checks()}
|
|
59
|
+
<Check
|
|
60
|
+
fn={async (screen) => {
|
|
61
|
+
expect(screen.getByRole('menu', { includeHidden: true })).not.toBeVisible();
|
|
62
|
+
await screen.getByRole('button', { name: 'Open Menu' }).click();
|
|
63
|
+
expect(screen.getByRole('menu')).toBeVisible();
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
{/snippet}
|
|
67
|
+
</Test>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
Install as part of your project's `devDependencies` through your preferred
|
|
73
|
+
package manager.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
npm install --save-dev svelte-declarative-testing
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Setup
|
|
80
|
+
|
|
81
|
+
There are 2 ways to activate your declarative svelte test files, using the
|
|
82
|
+
provided Vite plugins or by importing them directly into another test file to
|
|
83
|
+
mount them.
|
|
84
|
+
|
|
85
|
+
### Using the plugins
|
|
86
|
+
|
|
87
|
+
In order to work with the VSCode Vitest extension, 2 plugins are provided to
|
|
88
|
+
instrument the source code. You will also need to make sure Vitest includes your
|
|
89
|
+
test files:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
import svelteDeclarativeTesting from 'svelte-declarative-testing/vitest';
|
|
93
|
+
|
|
94
|
+
export default defineConfig({
|
|
95
|
+
plugins: [
|
|
96
|
+
// ...
|
|
97
|
+
|
|
98
|
+
...svelteDeclarativeTesting(),
|
|
99
|
+
],
|
|
100
|
+
|
|
101
|
+
// ...
|
|
102
|
+
|
|
103
|
+
test: {
|
|
104
|
+
include: [
|
|
105
|
+
'src/**/*.svelte.{test,spec}.{js,ts}',
|
|
106
|
+
|
|
107
|
+
// Includes .test.svelte and .spec.svelte
|
|
108
|
+
'src/**/*.{test,spec}.svelte',
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Importing directly into your other tests
|
|
115
|
+
|
|
116
|
+
> [!WARNING]
|
|
117
|
+
> This approach will not allow you to trigger test runs using the Vitest
|
|
118
|
+
> extension for VSCode. There may be some other side effects also.
|
|
119
|
+
|
|
120
|
+
If you don't want to use the plugins, simply import your Svelte-syntax test
|
|
121
|
+
files into your existing unit tests:
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
// Popover.test.js
|
|
125
|
+
import PopoverIntegrationTests from './Popover.test.svelte';
|
|
126
|
+
import { render } from '@testing-library/svelte'; // or vitest-browser-svelte
|
|
127
|
+
|
|
128
|
+
describe('Unit tests', () => {
|
|
129
|
+
// ... existing unit tests
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('Integration tests', () => {
|
|
133
|
+
render(PopoverIntegrationTests);
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Writing tests
|
|
138
|
+
|
|
139
|
+
Writing tests is quite intuitive. It is a similar structure to JavaScript test
|
|
140
|
+
files, with `describe()` and `test()` being replaced by `<Describe ...>` and
|
|
141
|
+
`<Test ...>` respectively. Additionally, there is a `<Check>` function that
|
|
142
|
+
allows you to provide the test functions.
|
|
143
|
+
|
|
144
|
+
Convenience wrappers are provided for @testing-library/svelte and
|
|
145
|
+
vitest-browser-svelte.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { Describe, Test, Check } from 'svelte-declarative-testing/testing-library';
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { Describe, Test, Check } from 'svelte-declarative-testing/vitest-browser-svelte';
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Alternatively, you can import the core functions and provide a custom `render()`
|
|
156
|
+
function to the `Test` component.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { Describe, Test, Check } from 'svelte-declarative-testing';
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### `<Describe ...>`
|
|
163
|
+
|
|
164
|
+
Wraps your tests in a suite, similare to `describe()`.
|
|
165
|
+
|
|
166
|
+
```svelte
|
|
167
|
+
<Describe label="My Integration Tests">
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Inside `Describe`, tests go in a `tests()` snippet. Any other children will be
|
|
171
|
+
rendered for each test.
|
|
172
|
+
|
|
173
|
+
| Properties | Type | Description |
|
|
174
|
+
| ---------- | ---------- | ------------------------------------------------------------------------------------- |
|
|
175
|
+
| `label` | `string` | The descriptive label for the test suite |
|
|
176
|
+
| `children` | `Snippet` | Any component(s) that you wish to render for the tests |
|
|
177
|
+
| `tests` | `Snippet` | A snippet block containing one or more `<Test>` elements |
|
|
178
|
+
| `skip` | `any` | Skips the suite if truthy, just like `describe.skip()` |
|
|
179
|
+
| `only` | `any` | Only run this suite if truthy, just like `describe.only()` |
|
|
180
|
+
| `todo` | `any` | Mark the suite as todo if truthy, just like `describe.todo()` |
|
|
181
|
+
| `shuffle` | `any` | Mark the suite as todo if truthy, just like `describe.todo()` |
|
|
182
|
+
| `skipIf` | `function` | Skips the suite if the function returns a truthy value, just like `describe.skipIf()` |
|
|
183
|
+
| `runIf` | `function` | Runs the suite if the function returns a truthy value, just like `describe.runIf()` |
|
|
184
|
+
|
|
185
|
+
```svelte
|
|
186
|
+
<Describe label="My Integration Tests">
|
|
187
|
+
<Button>My button</Button>
|
|
188
|
+
|
|
189
|
+
{#snippet tests()}
|
|
190
|
+
<Test it="renders an accessible button">
|
|
191
|
+
<!-- Test body goes here -->
|
|
192
|
+
</Test>
|
|
193
|
+
|
|
194
|
+
<Test it="does something when I click the button">
|
|
195
|
+
<!-- Test body goes here -->
|
|
196
|
+
</Test>
|
|
197
|
+
{/snippet}
|
|
198
|
+
</Describe>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### `<Test ...>`
|
|
202
|
+
|
|
203
|
+
Describes a test.
|
|
204
|
+
|
|
205
|
+
```svelte
|
|
206
|
+
<Test it="does what I expect it to do">
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Inside `Test`, checks go in a `checks()` snippet. Any other children will be
|
|
210
|
+
rendered for each test. If no other children are provided, the children of the
|
|
211
|
+
parent `<Describe>` are rendered.
|
|
212
|
+
|
|
213
|
+
| Properties | Type | Description |
|
|
214
|
+
| ---------- | ---------- | -------------------------------------------------------------------------------- |
|
|
215
|
+
| `it` | `string` | The description of the test |
|
|
216
|
+
| `children` | `Snippet` | Any component(s) that you wish to render for the test |
|
|
217
|
+
| `checks` | `Snippet` | A snippet block containing one or more `<Check>` elements |
|
|
218
|
+
| `skip` | `any` | Skips the test if truthy, just like `test.skip()` |
|
|
219
|
+
| `only` | `any` | Only run this test if truthy, just like `test.only()` |
|
|
220
|
+
| `todo` | `any` | Mark the test as todo if truthy, just like `test.todo()` |
|
|
221
|
+
| `fails` | `any` | Passes the test on failure if truthy, just like `test.fails()` |
|
|
222
|
+
| `skipIf` | `function` | Skips the test if the function returns a truthy value, just like `test.skipIf()` |
|
|
223
|
+
| `runIf` | `function` | Runs the test if the function returns a truthy value, just like `test.runIf()` |
|
|
224
|
+
| `render()` | `function` | Provides a custom render function (not available on the wrapper components` |
|
|
225
|
+
|
|
226
|
+
```svelte
|
|
227
|
+
<Test it="renders an accessible button">
|
|
228
|
+
<Button>My button</Button>
|
|
229
|
+
|
|
230
|
+
{#snippet checks()}
|
|
231
|
+
<Check fn={ /* ... */ } />
|
|
232
|
+
<Check fn={ /* ... */ } />
|
|
233
|
+
{/snippet}
|
|
234
|
+
</Describe>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### `<Check ...>`
|
|
238
|
+
|
|
239
|
+
Provides a test function.
|
|
240
|
+
|
|
241
|
+
```svelte
|
|
242
|
+
<Check fn={() => expect(1 + 1).toBe(2)} />
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
You can provide as many checks as you like and they will run sequentially.
|
|
246
|
+
|
|
247
|
+
| Properties | Type | Description |
|
|
248
|
+
| ---------- | ---------- | -------------------------------------------------- |
|
|
249
|
+
| `fn` | `function` | A function to be executed as part of the test body |
|
|
250
|
+
|
|
251
|
+
```svelte
|
|
252
|
+
<Test it="renders an accessible button">
|
|
253
|
+
<Button>My button</Button>
|
|
254
|
+
|
|
255
|
+
{#snippet checks()}
|
|
256
|
+
<Check fn={({queryByRole}) => {
|
|
257
|
+
expect(queryByRole("button")).toBeIntheDocument();
|
|
258
|
+
}/>
|
|
259
|
+
{/snippet}
|
|
260
|
+
</Describe>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Credits
|
|
264
|
+
|
|
265
|
+
This is not a new idea and was originally posited by [@7nik][7nik] and
|
|
266
|
+
[@paoloricciuti][paol] on the Svelte bug tracker. Thanks also to @sheremet-va,
|
|
267
|
+
who assisted with the Vitest test detection plugins.
|
|
268
|
+
|
|
269
|
+
[7nik]: https://github.com/sveltejs/svelte/issues/14791#issuecomment-3166064732
|
|
270
|
+
[paol]: https://github.com/sveltejs/svelte/issues/14791#issuecomment-3166186575
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "svelte-declarative-testing",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A way to mount your Svelte test components declaratively",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "src/index.js",
|
|
7
|
+
"main": "src/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./testing-library": {
|
|
10
|
+
"types": "./src/components/testing-library/index.d.ts",
|
|
11
|
+
"default": "./src/components/testing-library/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./vitest-browser-svelte": {
|
|
14
|
+
"types": "./src/components/vitest-browser-svelte/index.d.ts",
|
|
15
|
+
"default": "./src/components/vitest-browser-svelte/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./internal": {
|
|
18
|
+
"types": "./src/components/internal/index.d.ts",
|
|
19
|
+
"default": "./src/components/internal/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./vitest": {
|
|
22
|
+
"types": "./src/plugins/index.d.ts",
|
|
23
|
+
"default": "./src/plugins/vitest.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"svelte",
|
|
31
|
+
"vitest",
|
|
32
|
+
"test",
|
|
33
|
+
"testing-library"
|
|
34
|
+
],
|
|
35
|
+
"author": "Andy Earnshaw",
|
|
36
|
+
"license": "ISC",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"magic-string": "^0.30.21",
|
|
39
|
+
"ts-essentials": "^10.1.1",
|
|
40
|
+
"zimmerframe": "^1.1.4"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"eslint": "^9.39.2",
|
|
44
|
+
"prettier": "^3.8.1",
|
|
45
|
+
"prettier-plugin-svelte": "^3.4.1"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@testing-library/svelte": "^5.3.1",
|
|
49
|
+
"svelte": "^5.49.0",
|
|
50
|
+
"vitest": "^4.0.18",
|
|
51
|
+
"vitest-browser-svelte": "^2.0.2"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@testing-library/svelte": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"vitest-browser-svelte": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/** @import { DescribeProps } from './' */
|
|
3
|
+
import { describe } from 'vitest';
|
|
4
|
+
import { setAddTest, setSuiteRenderSnippet } from './context.js';
|
|
5
|
+
|
|
6
|
+
/**@type {DescribeProps}*/
|
|
7
|
+
const { label, todo, only, skip, skipIf, runIf, children, tests } = $props();
|
|
8
|
+
|
|
9
|
+
/**@type {((result: unknown) => void | Promise<void>)[]} */
|
|
10
|
+
const testFns = [];
|
|
11
|
+
|
|
12
|
+
setAddTest((fn) => {
|
|
13
|
+
testFns.push(fn);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// svelte-ignore state_referenced_locally
|
|
17
|
+
setSuiteRenderSnippet(children);
|
|
18
|
+
|
|
19
|
+
$effect(() => {
|
|
20
|
+
const describeFn = () => {
|
|
21
|
+
if (skip) return describe.skip;
|
|
22
|
+
if (skipIf) return describe.skipIf(skipIf());
|
|
23
|
+
if (todo) return describe.todo;
|
|
24
|
+
if (only) return describe.only;
|
|
25
|
+
if (runIf) return describe.runIf(runIf());
|
|
26
|
+
return describe;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describeFn()(label, async () => {
|
|
30
|
+
for (const test of testFns) {
|
|
31
|
+
await test();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
{@render tests()}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**@import { TestProps } from './index.js' */
|
|
3
|
+
import { test } from 'vitest';
|
|
4
|
+
import { getAddTest, setAddCheck, getSuiteRenderSnippet } from './context.js';
|
|
5
|
+
import Wrapper from './Wrapper.svelte';
|
|
6
|
+
|
|
7
|
+
/**@type {TestProps} */
|
|
8
|
+
const { it, fails, todo, only, skip, skipIf, runIf, children, checks, render } = $props();
|
|
9
|
+
|
|
10
|
+
/**@type {((result: unknown) => void | Promise<void>)[]} */
|
|
11
|
+
const checkFns = [];
|
|
12
|
+
|
|
13
|
+
const addTest = getAddTest();
|
|
14
|
+
|
|
15
|
+
const suiteRenderSnippet = getSuiteRenderSnippet();
|
|
16
|
+
|
|
17
|
+
setAddCheck((fn) => {
|
|
18
|
+
checkFns.push(fn);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const setupTest = () => {
|
|
22
|
+
const testFn = () => {
|
|
23
|
+
if (skip) return test.skip;
|
|
24
|
+
if (skipIf) return test.skipIf(skipIf());
|
|
25
|
+
if (todo) return test.todo;
|
|
26
|
+
if (only) return test.only;
|
|
27
|
+
if (fails) return test.fails;
|
|
28
|
+
if (runIf) return test.runIf(runIf());
|
|
29
|
+
return test;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
testFn()(it, async () => {
|
|
33
|
+
const result = await render(Wrapper, { children: children ?? suiteRenderSnippet });
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
if (!checkFns.length) {
|
|
37
|
+
throw new Error(`No checks were registered for test: "${it}"`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const check of checkFns) {
|
|
41
|
+
await check(result);
|
|
42
|
+
}
|
|
43
|
+
} finally {
|
|
44
|
+
result.unmount();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
addTest?.(setupTest);
|
|
50
|
+
|
|
51
|
+
$effect(() => {
|
|
52
|
+
if (!addTest) setupTest();
|
|
53
|
+
});
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
{@render checks()}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createContext, Snippet } from 'svelte';
|
|
2
|
+
|
|
3
|
+
export const [getAddTest, setAddTest] = createContext<(fn: () => void | Promise<void>) => void>();
|
|
4
|
+
|
|
5
|
+
export const [getAddCheck, setAddCheck] = createContext<(fn: () => void | Promise<void>) => void>();
|
|
6
|
+
|
|
7
|
+
export const [getSuiteRenderSnippet, setSuiteRenderSnippet] = createContext<Snippet | undefined>();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Snippet, Component } from 'svelte';
|
|
2
|
+
import { XOR } from 'ts-essentials';
|
|
3
|
+
|
|
4
|
+
export type ModifierProps = XOR<
|
|
5
|
+
/* These are all mutually exclusive */
|
|
6
|
+
{ only: boolean },
|
|
7
|
+
{ todo: boolean },
|
|
8
|
+
{ skip: boolean },
|
|
9
|
+
{ skipIf: () => unknown },
|
|
10
|
+
{ runIf: () => unknown }
|
|
11
|
+
>;
|
|
12
|
+
|
|
13
|
+
export type DescribeProps =
|
|
14
|
+
| {
|
|
15
|
+
label: string;
|
|
16
|
+
children?: Snippet;
|
|
17
|
+
tests: Snippet;
|
|
18
|
+
}
|
|
19
|
+
| ModifierProps;
|
|
20
|
+
|
|
21
|
+
export type BaseTestProps =
|
|
22
|
+
| {
|
|
23
|
+
it: string;
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
checks: Snippet;
|
|
26
|
+
}
|
|
27
|
+
| XOR<ModifierProps, { fails: boolean }>;
|
|
28
|
+
|
|
29
|
+
export type TestProps = BaseTestProps & {
|
|
30
|
+
render: (result: unknown) => void | Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type CheckProps = {
|
|
34
|
+
fn: (result: unknown) => void | Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export declare const Describe: Component<DescribeProps>;
|
|
38
|
+
export declare const Test: Component<TestProps>;
|
|
39
|
+
export declare const Check: Component<CheckProps>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**@import { BaseTestProps } from '../core' */
|
|
3
|
+
|
|
4
|
+
import { Test as CoreTest } from '../core';
|
|
5
|
+
import { render } from '@testing-library/svelte';
|
|
6
|
+
|
|
7
|
+
/**@type {BaseTestProps}*/
|
|
8
|
+
const props = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<CoreTest {render} {...props} />
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RenderResult } from '@testing-library/svelte';
|
|
2
|
+
import { Component } from 'svelte';
|
|
3
|
+
import type { BaseTestProps, DescribeProps } from '../core';
|
|
4
|
+
|
|
5
|
+
export type CheckFn = (result: RenderResult<Component<any, any>>) => void | Promise<void>;
|
|
6
|
+
|
|
7
|
+
export type CheckProps = {
|
|
8
|
+
fn: CheckFn;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { DescribeProps };
|
|
12
|
+
export declare const Describe: Component<DescribeProps>;
|
|
13
|
+
export declare const Test: Component<BaseTestProps>;
|
|
14
|
+
export declare const Check: Component<CheckProps>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**@import { BaseTestProps } from '../core' */
|
|
3
|
+
|
|
4
|
+
import { Test as CoreTest } from '../core';
|
|
5
|
+
import { render } from 'vitest-browser-svelte';
|
|
6
|
+
|
|
7
|
+
/**@type {BaseTestProps}*/
|
|
8
|
+
const props = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<CoreTest {render} {...props} />
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RenderResult } from 'vitest-browser-svelte';
|
|
2
|
+
import { Component } from 'svelte';
|
|
3
|
+
import type { BaseTestProps, DescribeProps } from '../core';
|
|
4
|
+
|
|
5
|
+
export type CheckFn = (result: RenderResult<Component<any, any>>) => void | Promise<void>;
|
|
6
|
+
|
|
7
|
+
export type CheckProps = {
|
|
8
|
+
fn: CheckFn;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { DescribeProps };
|
|
12
|
+
export declare const Test: Component<BaseTestProps>;
|
|
13
|
+
export declare const Check: Component<CheckProps>;
|
|
14
|
+
export declare const Describe: Component<DescribeProps>;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** @import type { Plugin } from 'vitest/config' */
|
|
2
|
+
import { parse } from 'svelte/compiler';
|
|
3
|
+
import { walk } from 'zimmerframe';
|
|
4
|
+
import MagicString from 'magic-string';
|
|
5
|
+
|
|
6
|
+
const pre = () =>
|
|
7
|
+
/**@type {Plugin}*/ ({
|
|
8
|
+
name: 'transform-svelte-declarative-test',
|
|
9
|
+
enforce: 'pre',
|
|
10
|
+
transform(code, id) {
|
|
11
|
+
if (!/\.(?:test|spec)\.svelte$/.test(id)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const s = new MagicString(code);
|
|
16
|
+
const ast = parse(code);
|
|
17
|
+
walk(
|
|
18
|
+
ast.html,
|
|
19
|
+
{},
|
|
20
|
+
{
|
|
21
|
+
InlineComponent(node, { visit }) {
|
|
22
|
+
if (node.name === 'Test') {
|
|
23
|
+
for (const attr of node.attributes) {
|
|
24
|
+
if (attr.name === 'it') {
|
|
25
|
+
const name =
|
|
26
|
+
attr.value?.[0]?.data?.replace(/"/g, '\\"') ?? '(dynamically named test)';
|
|
27
|
+
|
|
28
|
+
s.appendLeft(
|
|
29
|
+
attr.start,
|
|
30
|
+
`data-test-code={function () { test("${name}", () => {}) }} `,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return;
|
|
36
|
+
} else if (node.name === 'Describe') {
|
|
37
|
+
for (const attr of node.attributes) {
|
|
38
|
+
if (attr.name === 'label') {
|
|
39
|
+
const name =
|
|
40
|
+
attr.value?.[0]?.data?.replace(/"/g, '\\"') ?? '(dynamically named test suite)';
|
|
41
|
+
|
|
42
|
+
s.appendLeft(
|
|
43
|
+
attr.start,
|
|
44
|
+
`data-describe-code={function () { describe("${name}", () => {}) }} `,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const child of node.children) {
|
|
50
|
+
if (child.type === 'SnippetBlock') {
|
|
51
|
+
visit(child);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
SnippetBlock(node, { visit }) {
|
|
57
|
+
for (const child of node.children) {
|
|
58
|
+
if (child.type === 'InlineComponent') {
|
|
59
|
+
visit(child);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
console.log(s.toString());
|
|
67
|
+
return { code: s.toString(), map: s.generateMap({ hires: true }) };
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const post = () =>
|
|
72
|
+
/**@type {Plugin}*/ ({
|
|
73
|
+
name: 'transform-svelte-declarative-test',
|
|
74
|
+
enforce: 'post',
|
|
75
|
+
transform(code, id) {
|
|
76
|
+
if (!/\.(?:test|spec)\.svelte$/.test(id)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const s = new MagicString(code);
|
|
81
|
+
const componentName = id.split('/').pop()?.split('.').slice(0, -1).join('_');
|
|
82
|
+
s.append(`
|
|
83
|
+
import { mount } from 'svelte';
|
|
84
|
+
mount(${componentName}, { target: document.body });
|
|
85
|
+
`);
|
|
86
|
+
|
|
87
|
+
return { code: s.toString(), map: s.generateMap({ hires: true }) };
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export default function getPlugins() {
|
|
92
|
+
return [pre(), post()];
|
|
93
|
+
}
|