@yabbadabbadev/pepito 0.1.1 → 0.2.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/CHANGELOG.md +7 -0
- package/README.md +88 -22
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/mount-core.d.ts +38 -0
- package/dist/mount-core.js +44 -0
- package/dist/network.d.ts +2 -2
- package/dist/network.js +2 -2
- package/dist/react.d.ts +28 -0
- package/dist/react.js +29 -0
- package/dist/setup-network.d.ts +2 -2
- package/dist/setup-network.js +2 -2
- package/dist/storage-cleanup.d.ts +2 -2
- package/dist/storage-cleanup.js +2 -2
- package/dist/svelte.d.ts +23 -0
- package/dist/svelte.js +25 -0
- package/dist/traffic-registry.d.ts +3 -3
- package/dist/traffic-registry.js +2 -2
- package/dist/vue.d.ts +23 -0
- package/dist/vue.js +25 -0
- package/package.json +20 -6
- package/dist/mount.d.ts +0 -45
- package/dist/mount.js +0 -61
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0](https://github.com/yabbadabbadev/pepito/compare/v0.1.1...v0.2.0) (2026-09-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* framework-agnostic core + subpath adapters for /react, /vue, /svelte ([#9](https://github.com/yabbadabbadev/pepito/issues/9)) ([2a1ab74](https://github.com/yabbadabbadev/pepito/commit/2a1ab74274d8c8ac03980f368cde8260c7330f3a))
|
|
9
|
+
|
|
3
10
|
## [0.1.1](https://github.com/yabbadabbadev/pepito/compare/v0.1.0...v0.1.1) (2026-08-20)
|
|
4
11
|
|
|
5
12
|
Nothing changed for consumers of this package: no new API, no fix, no
|
package/README.md
CHANGED
|
@@ -8,21 +8,20 @@ matchers to query it.
|
|
|
8
8
|
|
|
9
9
|
## 1. Install and start
|
|
10
10
|
|
|
11
|
-
`
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
`vitest` and `msw` are **peerDependencies**: install them if your project
|
|
12
|
+
doesn't already have them.
|
|
13
|
+
|
|
14
|
+
### Core (any framework)
|
|
15
|
+
|
|
16
|
+
The core — `setupNetwork`, matchers, request descriptors, `network.log()` —
|
|
17
|
+
works without any framework adapter.
|
|
14
18
|
|
|
15
19
|
```bash
|
|
16
|
-
npm i -D @yabbadabbadev/pepito msw
|
|
20
|
+
npm i -D @yabbadabbadev/pepito msw
|
|
17
21
|
npx msw init public --save
|
|
18
22
|
```
|
|
19
23
|
|
|
20
|
-
`
|
|
21
|
-
browser mode (`public/mockServiceWorker.js`); it's a one-off, not a
|
|
22
|
-
day-to-day step — it's only repeated when upgrading MSW's version.
|
|
23
|
-
|
|
24
|
-
Call `setupNetwork` once, in a `setupFiles` file of `vitest.config` — never
|
|
25
|
-
inside a test:
|
|
24
|
+
Call `setupNetwork` once, in a `setupFiles` file of `vitest.config`:
|
|
26
25
|
|
|
27
26
|
```ts
|
|
28
27
|
// vitest.setup.ts
|
|
@@ -42,6 +41,70 @@ export default defineConfig({
|
|
|
42
41
|
})
|
|
43
42
|
```
|
|
44
43
|
|
|
44
|
+
### React
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm i -D @yabbadabbadev/pepito msw vitest-browser-react
|
|
48
|
+
npx msw init public --save
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { mount } from '@yabbadabbadev/pepito/react'
|
|
53
|
+
import { App } from '../src/App'
|
|
54
|
+
|
|
55
|
+
const screen = await mount(<App />, { path: '/products' })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Vue
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm i -D @yabbadabbadev/pepito msw vitest-browser-vue
|
|
62
|
+
npx msw init public --save
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { mount } from '@yabbadabbadev/pepito/vue'
|
|
67
|
+
import App from '../src/App.vue'
|
|
68
|
+
|
|
69
|
+
const screen = await mount(App, { path: '/products' })
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Svelte
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm i -D @yabbadabbadev/pepito msw vitest-browser-svelte
|
|
76
|
+
npx msw init public --save
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { mount } from '@yabbadabbadev/pepito/svelte'
|
|
81
|
+
import App from '../src/App.svelte'
|
|
82
|
+
|
|
83
|
+
const screen = await mount(App, { path: '/products' })
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Custom framework adapter
|
|
87
|
+
|
|
88
|
+
If you use a framework without an official subpath (Lit, Preact, Angular,
|
|
89
|
+
Solid, …), build your own adapter with `mountCore`:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { render } from 'vitest-browser-lit'
|
|
93
|
+
import { mountCore } from '@yabbadabbadev/pepito'
|
|
94
|
+
|
|
95
|
+
export function mount(
|
|
96
|
+
component: unknown,
|
|
97
|
+
options?: Parameters<typeof mountCore>[2],
|
|
98
|
+
) {
|
|
99
|
+
return mountCore(component, (c) => render(c as any), options)
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`mountCore` applies `pushState` for routing and registers test-specific
|
|
104
|
+
MSW handlers before calling your `render` function. Its return type is
|
|
105
|
+
generic: it infers the full typed result from whatever your `render`
|
|
106
|
+
returns.
|
|
107
|
+
|
|
45
108
|
`setupNetwork`'s second argument passes straight through to
|
|
46
109
|
`worker.start()`, with no wrapper of its own — for example, to make a
|
|
47
110
|
request with no handler fail the test instead of just warning on the
|
|
@@ -55,23 +118,26 @@ import { handlers } from './handlers'
|
|
|
55
118
|
setupNetwork(handlers, { onUnhandledRequest: 'error' })
|
|
56
119
|
```
|
|
57
120
|
|
|
58
|
-
Importing anything from `pepito` —
|
|
59
|
-
network matchers along as `expect` types: there's no separate
|
|
60
|
-
registration. If your test `tsconfig` doesn't include the setup
|
|
61
|
-
won't see the augmentation and
|
|
62
|
-
raise `TS2339` even though the
|
|
121
|
+
Importing anything from `pepito` — even just `setupNetwork` — already
|
|
122
|
+
brings the network matchers along as `expect` types: there's no separate
|
|
123
|
+
type registration. If your test `tsconfig` doesn't include the setup
|
|
124
|
+
file, `tsc` won't see the augmentation and
|
|
125
|
+
`expect(...).toHaveBeenRequested()` will raise `TS2339` even though the
|
|
126
|
+
test passes at runtime.
|
|
63
127
|
|
|
64
128
|
## 2. Mount the application
|
|
65
129
|
|
|
66
|
-
`mount`
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
130
|
+
`mount` (from `@yabbadabbadev/pepito/react`) mounts with
|
|
131
|
+
`vitest-browser-react` and returns its `screen` unwrapped. It requires
|
|
132
|
+
`setupNetwork` to have run first (section 1), **even for a test with no
|
|
133
|
+
network**: the coupling is deliberate — `mount` also installs URL and
|
|
134
|
+
storage cleanup between tests, not just the network — and if it's missing,
|
|
135
|
+
it fails immediately with a fix instruction.
|
|
71
136
|
|
|
72
137
|
```tsx
|
|
73
138
|
import { http, HttpResponse } from 'msw'
|
|
74
|
-
import {
|
|
139
|
+
import { get } from '@yabbadabbadev/pepito'
|
|
140
|
+
import { mount } from '@yabbadabbadev/pepito/react'
|
|
75
141
|
import { App } from '../src/App'
|
|
76
142
|
import { ProductListMother } from '../test/mothers/product-list-mother'
|
|
77
143
|
|
|
@@ -102,7 +168,7 @@ route, and `setupNetwork()` undoes them afterwards in its `afterEach`.
|
|
|
102
168
|
Neither option is required — `mount(<App />)` on its own just mounts:
|
|
103
169
|
|
|
104
170
|
```tsx
|
|
105
|
-
import { mount } from '@yabbadabbadev/pepito'
|
|
171
|
+
import { mount } from '@yabbadabbadev/pepito/react'
|
|
106
172
|
import { App } from '../src/App'
|
|
107
173
|
|
|
108
174
|
test('mounts with no path or network of its own', async () => {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ import './matcher-types';
|
|
|
3
3
|
export * from './request-descriptors';
|
|
4
4
|
export { setupNetwork } from './setup-network';
|
|
5
5
|
export { network } from './network';
|
|
6
|
-
export {
|
|
7
|
-
export type { MountOptions } from './mount';
|
|
6
|
+
export { mountCore } from './mount-core';
|
|
7
|
+
export type { MountOptions, MountResult } from './mount-core';
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { RequestHandler } from 'msw';
|
|
2
|
+
/** Options for {@link mountCore}. Both are optional. */
|
|
3
|
+
export interface MountOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Same-origin URI starting with `/`, query and hash included (for example
|
|
6
|
+
* `/products?filter=bread#detail`). Applied with `history.pushState`
|
|
7
|
+
* before render so the application's router reads it on mount.
|
|
8
|
+
*/
|
|
9
|
+
path?: string;
|
|
10
|
+
/**
|
|
11
|
+
* MSW handlers of this test's own. Installed with `worker.use()` before
|
|
12
|
+
* render, so they take priority over the suite's for the same route, and
|
|
13
|
+
* `setupNetwork()` undoes them in its `afterEach`.
|
|
14
|
+
*/
|
|
15
|
+
network?: RequestHandler[];
|
|
16
|
+
}
|
|
17
|
+
/** The minimal contract every `vitest-browser-*` render result satisfies. */
|
|
18
|
+
export interface MountResult {
|
|
19
|
+
container: Element;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Framework-agnostic mount: applies `pushState` (if `path`), registers
|
|
23
|
+
* test-specific MSW handlers (if `network`), then calls `render(component)`.
|
|
24
|
+
*
|
|
25
|
+
* The `render` function is provided by the caller — each framework subpath
|
|
26
|
+
* (`/react`, `/vue`, `/svelte`) wraps this with its own `vitest-browser-*`
|
|
27
|
+
* render. See the "Custom framework adapter" section in the README for
|
|
28
|
+
* building your own.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { render } from 'vitest-browser-lit'
|
|
33
|
+
* import { mountCore } from '@yabbadabbadev/pepito'
|
|
34
|
+
*
|
|
35
|
+
* const screen = await mountCore(MyComponent, (c) => render(c as any))
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function mountCore<T extends MountResult>(component: unknown, render: (component: unknown) => T | Promise<T>, options?: MountOptions): Promise<T>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { requireNetworkContext } from './network-singleton';
|
|
2
|
+
function isSameOriginPath(path) {
|
|
3
|
+
if (!path.startsWith('/'))
|
|
4
|
+
return false;
|
|
5
|
+
try {
|
|
6
|
+
return new URL(path, location.origin).origin === location.origin;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Framework-agnostic mount: applies `pushState` (if `path`), registers
|
|
14
|
+
* test-specific MSW handlers (if `network`), then calls `render(component)`.
|
|
15
|
+
*
|
|
16
|
+
* The `render` function is provided by the caller — each framework subpath
|
|
17
|
+
* (`/react`, `/vue`, `/svelte`) wraps this with its own `vitest-browser-*`
|
|
18
|
+
* render. See the "Custom framework adapter" section in the README for
|
|
19
|
+
* building your own.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { render } from 'vitest-browser-lit'
|
|
24
|
+
* import { mountCore } from '@yabbadabbadev/pepito'
|
|
25
|
+
*
|
|
26
|
+
* const screen = await mountCore(MyComponent, (c) => render(c as any))
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export async function mountCore(component, render, options = {}) {
|
|
30
|
+
const { worker } = requireNetworkContext('mountCore');
|
|
31
|
+
const { path, network: testHandlers } = options;
|
|
32
|
+
if (path !== undefined && !isSameOriginPath(path)) {
|
|
33
|
+
throw new Error(`pepito: path must be a same-origin URI that starts with '/'; ` +
|
|
34
|
+
`received: ${path}. A different origin is mocked in the MSW ` +
|
|
35
|
+
`handlers, not in the mount.`);
|
|
36
|
+
}
|
|
37
|
+
if (path !== undefined) {
|
|
38
|
+
history.pushState({}, '', path);
|
|
39
|
+
}
|
|
40
|
+
if (testHandlers !== undefined && testHandlers.length > 0) {
|
|
41
|
+
worker.use(...testHandlers);
|
|
42
|
+
}
|
|
43
|
+
return await Promise.resolve(render(component));
|
|
44
|
+
}
|
package/dist/network.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare const network: {
|
|
|
34
34
|
* absence of future traffic: the same practical blind window remains as
|
|
35
35
|
* for the rest of the mechanism — a request fired in the same tick as the
|
|
36
36
|
* call, before crossing the real round trip to the service worker (1–6 ms
|
|
37
|
-
* measured; see `.
|
|
37
|
+
* measured; see `.agents/docs/references/measured-foundations.md`), may
|
|
38
38
|
* not be in the registry yet when `network.idle()` resolves.
|
|
39
39
|
*
|
|
40
40
|
* Meant for visual regression: capturing right after mounting, with a
|
|
@@ -42,7 +42,7 @@ export declare const network: {
|
|
|
42
42
|
* native stabilizer of `toMatchScreenshot` doesn't catch it because
|
|
43
43
|
* "Loading…" is also a capture that stops changing between frames
|
|
44
44
|
* (measured: 0 failures across 17 local runs + 3 in CI waiting for calm
|
|
45
|
-
* this way — see `.
|
|
45
|
+
* this way — see `.agents/docs/references/measured-foundations.md`).
|
|
46
46
|
* Before this, the only public way to wait for calm on its own was to
|
|
47
47
|
* divert `expect.network().toHaveNoUnhandledRequests()` from its actual
|
|
48
48
|
* purpose (detecting traffic without a handler). It works just as well as a
|
package/dist/network.js
CHANGED
|
@@ -45,7 +45,7 @@ export const network = {
|
|
|
45
45
|
* absence of future traffic: the same practical blind window remains as
|
|
46
46
|
* for the rest of the mechanism — a request fired in the same tick as the
|
|
47
47
|
* call, before crossing the real round trip to the service worker (1–6 ms
|
|
48
|
-
* measured; see `.
|
|
48
|
+
* measured; see `.agents/docs/references/measured-foundations.md`), may
|
|
49
49
|
* not be in the registry yet when `network.idle()` resolves.
|
|
50
50
|
*
|
|
51
51
|
* Meant for visual regression: capturing right after mounting, with a
|
|
@@ -53,7 +53,7 @@ export const network = {
|
|
|
53
53
|
* native stabilizer of `toMatchScreenshot` doesn't catch it because
|
|
54
54
|
* "Loading…" is also a capture that stops changing between frames
|
|
55
55
|
* (measured: 0 failures across 17 local runs + 3 in CI waiting for calm
|
|
56
|
-
* this way — see `.
|
|
56
|
+
* this way — see `.agents/docs/references/measured-foundations.md`).
|
|
57
57
|
* Before this, the only public way to wait for calm on its own was to
|
|
58
58
|
* divert `expect.network().toHaveNoUnhandledRequests()` from its actual
|
|
59
59
|
* purpose (detecting traffic without a handler). It works just as well as a
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import { type RenderResult } from 'vitest-browser-react';
|
|
3
|
+
import type { MountOptions } from './mount-core';
|
|
4
|
+
/**
|
|
5
|
+
* Mounts a React element with `vitest-browser-react`, optionally on a real
|
|
6
|
+
* document route and with test-specific MSW handlers.
|
|
7
|
+
*
|
|
8
|
+
* `path`, if given, has to be a same-origin URI starting with `/`: it's
|
|
9
|
+
* applied with `history.pushState` BEFORE render because the application's
|
|
10
|
+
* `BrowserRouter` reads the URL on mount and only listens to `popstate`
|
|
11
|
+
* afterwards. The URI can carry query and hash: they flow through the router
|
|
12
|
+
* the same as the path. `setupNetwork()` restores the original URL in its
|
|
13
|
+
* `afterEach`, so every test starts from the same route regardless of what
|
|
14
|
+
* the previous one mounted.
|
|
15
|
+
*
|
|
16
|
+
* `network`, if given, is registered with `worker.use()` before render: its
|
|
17
|
+
* handlers take priority over the suite's for this request, with the same
|
|
18
|
+
* resolution rules as MSW.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* import { mount } from '@yabbadabbadev/pepito/react'
|
|
23
|
+
*
|
|
24
|
+
* const screen = await mount(<App />, { path: '/products?filter=bread' })
|
|
25
|
+
* await expect.element(screen.getByText('filter: bread')).toBeVisible()
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function mount(ui: ReactElement, options?: MountOptions): Promise<RenderResult>;
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { render } from 'vitest-browser-react';
|
|
2
|
+
import { mountCore } from './mount-core';
|
|
3
|
+
/**
|
|
4
|
+
* Mounts a React element with `vitest-browser-react`, optionally on a real
|
|
5
|
+
* document route and with test-specific MSW handlers.
|
|
6
|
+
*
|
|
7
|
+
* `path`, if given, has to be a same-origin URI starting with `/`: it's
|
|
8
|
+
* applied with `history.pushState` BEFORE render because the application's
|
|
9
|
+
* `BrowserRouter` reads the URL on mount and only listens to `popstate`
|
|
10
|
+
* afterwards. The URI can carry query and hash: they flow through the router
|
|
11
|
+
* the same as the path. `setupNetwork()` restores the original URL in its
|
|
12
|
+
* `afterEach`, so every test starts from the same route regardless of what
|
|
13
|
+
* the previous one mounted.
|
|
14
|
+
*
|
|
15
|
+
* `network`, if given, is registered with `worker.use()` before render: its
|
|
16
|
+
* handlers take priority over the suite's for this request, with the same
|
|
17
|
+
* resolution rules as MSW.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import { mount } from '@yabbadabbadev/pepito/react'
|
|
22
|
+
*
|
|
23
|
+
* const screen = await mount(<App />, { path: '/products?filter=bread' })
|
|
24
|
+
* await expect.element(screen.getByText('filter: bread')).toBeVisible()
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export async function mount(ui, options) {
|
|
28
|
+
return mountCore(ui, (c) => render(c), options);
|
|
29
|
+
}
|
package/dist/setup-network.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { SetupWorker, StartOptions } from 'msw/browser';
|
|
|
4
4
|
* Starts the MSW worker and hooks up the traffic registry, leaving the
|
|
5
5
|
* between-test cleanup installed in `afterEach`: registry, hot handlers,
|
|
6
6
|
* document URL and origin storage all return to their pre-test state
|
|
7
|
-
* (measured — see `.
|
|
7
|
+
* (measured — see `.agents/docs/references/measured-foundations.md`).
|
|
8
8
|
*
|
|
9
9
|
* Called once per test file, typically from a `setupFiles` entry in
|
|
10
10
|
* `vitest.config`, never from inside a test.
|
|
@@ -14,7 +14,7 @@ import type { SetupWorker, StartOptions } from 'msw/browser';
|
|
|
14
14
|
* survives between tests — see `clearOriginStorage` in storage-cleanup.ts.
|
|
15
15
|
* A cookie without an explicit `path` does get cleared, including one set
|
|
16
16
|
* while `mount` was simulating being on a nested route (measured — see
|
|
17
|
-
* `.
|
|
17
|
+
* `.agents/docs/references/measured-foundations.md`).
|
|
18
18
|
*
|
|
19
19
|
* @param handlers - Initial MSW handlers, the same ones `setupWorker` would take.
|
|
20
20
|
* @param startOptions - Passed through to `worker.start()` as-is; no wrapper of its own.
|
package/dist/setup-network.js
CHANGED
|
@@ -8,7 +8,7 @@ import { resetTraffic } from './traffic-registry';
|
|
|
8
8
|
* Starts the MSW worker and hooks up the traffic registry, leaving the
|
|
9
9
|
* between-test cleanup installed in `afterEach`: registry, hot handlers,
|
|
10
10
|
* document URL and origin storage all return to their pre-test state
|
|
11
|
-
* (measured — see `.
|
|
11
|
+
* (measured — see `.agents/docs/references/measured-foundations.md`).
|
|
12
12
|
*
|
|
13
13
|
* Called once per test file, typically from a `setupFiles` entry in
|
|
14
14
|
* `vitest.config`, never from inside a test.
|
|
@@ -18,7 +18,7 @@ import { resetTraffic } from './traffic-registry';
|
|
|
18
18
|
* survives between tests — see `clearOriginStorage` in storage-cleanup.ts.
|
|
19
19
|
* A cookie without an explicit `path` does get cleared, including one set
|
|
20
20
|
* while `mount` was simulating being on a nested route (measured — see
|
|
21
|
-
* `.
|
|
21
|
+
* `.agents/docs/references/measured-foundations.md`).
|
|
22
22
|
*
|
|
23
23
|
* @param handlers - Initial MSW handlers, the same ones `setupWorker` would take.
|
|
24
24
|
* @param startOptions - Passed through to `worker.start()` as-is; no wrapper of its own.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* land on the same worker: `localStorage`, `sessionStorage` and cookies
|
|
4
4
|
* belong to the origin, not the document, so they survive per-file
|
|
5
5
|
* isolation (measured — see
|
|
6
|
-
* `.
|
|
6
|
+
* `.agents/docs/references/measured-foundations.md`).
|
|
7
7
|
*
|
|
8
8
|
* Each cookie is expired twice per name: once with `path=/` (the one most
|
|
9
9
|
* application code sets) and once with no `path` attribute, in case one was
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Measured that it doesn't, in this harness (Chromium via Playwright,
|
|
18
18
|
* `vitest@4.1.10`): the order has no observable effect today — full
|
|
19
|
-
* evidence in `.
|
|
19
|
+
* evidence in `.agents/docs/references/measured-foundations.md`. The order
|
|
20
20
|
* is kept anyway, at no cost, in case some future runner or browser does
|
|
21
21
|
* follow the simulated URL.
|
|
22
22
|
*
|
package/dist/storage-cleanup.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* land on the same worker: `localStorage`, `sessionStorage` and cookies
|
|
4
4
|
* belong to the origin, not the document, so they survive per-file
|
|
5
5
|
* isolation (measured — see
|
|
6
|
-
* `.
|
|
6
|
+
* `.agents/docs/references/measured-foundations.md`).
|
|
7
7
|
*
|
|
8
8
|
* Each cookie is expired twice per name: once with `path=/` (the one most
|
|
9
9
|
* application code sets) and once with no `path` attribute, in case one was
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Measured that it doesn't, in this harness (Chromium via Playwright,
|
|
18
18
|
* `vitest@4.1.10`): the order has no observable effect today — full
|
|
19
|
-
* evidence in `.
|
|
19
|
+
* evidence in `.agents/docs/references/measured-foundations.md`. The order
|
|
20
20
|
* is kept anyway, at no cost, in case some future runner or browser does
|
|
21
21
|
* follow the simulated URL.
|
|
22
22
|
*
|
package/dist/svelte.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { render, type RenderResult } from 'vitest-browser-svelte';
|
|
2
|
+
import type { MountOptions } from './mount-core';
|
|
3
|
+
/**
|
|
4
|
+
* Mounts a Svelte component with `vitest-browser-svelte`, optionally on a
|
|
5
|
+
* real document route and with test-specific MSW handlers.
|
|
6
|
+
*
|
|
7
|
+
* `path`, if given, has to be a same-origin URI starting with `/`: it's
|
|
8
|
+
* applied with `history.pushState` BEFORE render so the application's
|
|
9
|
+
* router reads it on mount. `setupNetwork()` restores the original URL
|
|
10
|
+
* in its `afterEach`.
|
|
11
|
+
*
|
|
12
|
+
* `network`, if given, is registered with `worker.use()` before render.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { mount } from '@yabbadabbadev/pepito/svelte'
|
|
17
|
+
* import App from './App.svelte'
|
|
18
|
+
*
|
|
19
|
+
* const screen = await mount(App, { path: '/products' })
|
|
20
|
+
* await expect.element(screen.getByText('Products')).toBeVisible()
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function mount(component: Parameters<typeof render>[0], options?: MountOptions): Promise<RenderResult<any>>;
|
package/dist/svelte.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { render } from 'vitest-browser-svelte';
|
|
2
|
+
import { mountCore } from './mount-core';
|
|
3
|
+
/**
|
|
4
|
+
* Mounts a Svelte component with `vitest-browser-svelte`, optionally on a
|
|
5
|
+
* real document route and with test-specific MSW handlers.
|
|
6
|
+
*
|
|
7
|
+
* `path`, if given, has to be a same-origin URI starting with `/`: it's
|
|
8
|
+
* applied with `history.pushState` BEFORE render so the application's
|
|
9
|
+
* router reads it on mount. `setupNetwork()` restores the original URL
|
|
10
|
+
* in its `afterEach`.
|
|
11
|
+
*
|
|
12
|
+
* `network`, if given, is registered with `worker.use()` before render.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { mount } from '@yabbadabbadev/pepito/svelte'
|
|
17
|
+
* import App from './App.svelte'
|
|
18
|
+
*
|
|
19
|
+
* const screen = await mount(App, { path: '/products' })
|
|
20
|
+
* await expect.element(screen.getByText('Products')).toBeVisible()
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export async function mount(component, options) {
|
|
24
|
+
return mountCore(component, (c) => render(c), options);
|
|
25
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Traffic entry as it lives in the registry while the test runs: `body` and
|
|
3
3
|
* `responseBody` are promises because they can only be read once (measured
|
|
4
|
-
* — see `.
|
|
4
|
+
* — see `.agents/docs/references/measured-foundations.md`) and are stored
|
|
5
5
|
* without awaiting, so as not to block the `request:start` listener that
|
|
6
6
|
* opens them.
|
|
7
7
|
*/
|
|
@@ -26,7 +26,7 @@ export interface ResolvedRequest extends Omit<ObservedRequest, 'body' | 'respons
|
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Opens a traffic entry with what can only be read at `request:start`
|
|
29
|
-
* (measured — see `.
|
|
29
|
+
* (measured — see `.agents/docs/references/measured-foundations.md`) and
|
|
30
30
|
* marks it in flight.
|
|
31
31
|
* `watchNetwork` (msw-events.ts) is the only caller.
|
|
32
32
|
*/
|
|
@@ -73,7 +73,7 @@ export declare const QUIESCENCE_TIMEOUT_MS = 4000;
|
|
|
73
73
|
* in the same tick as the call hasn't yet crossed the real round trip to
|
|
74
74
|
* the service worker that registers its `request:start`, so this function
|
|
75
75
|
* reads the counter at zero by construction, not by actual absence (see
|
|
76
|
-
* `.
|
|
76
|
+
* `.agents/docs/references/measured-foundations.md`). Whoever needs to
|
|
77
77
|
* assert an absence or count with precision must go through
|
|
78
78
|
* `snapshotAfterIdle` in `pepito/src/matchers.ts`, which closes that window
|
|
79
79
|
* with a two-observation stability condition; calling this function
|
package/dist/traffic-registry.js
CHANGED
|
@@ -6,7 +6,7 @@ const traffic = new Map();
|
|
|
6
6
|
const pending = new Set();
|
|
7
7
|
/**
|
|
8
8
|
* Opens a traffic entry with what can only be read at `request:start`
|
|
9
|
-
* (measured — see `.
|
|
9
|
+
* (measured — see `.agents/docs/references/measured-foundations.md`) and
|
|
10
10
|
* marks it in flight.
|
|
11
11
|
* `watchNetwork` (msw-events.ts) is the only caller.
|
|
12
12
|
*/
|
|
@@ -99,7 +99,7 @@ export const QUIESCENCE_TIMEOUT_MS = 4000;
|
|
|
99
99
|
* in the same tick as the call hasn't yet crossed the real round trip to
|
|
100
100
|
* the service worker that registers its `request:start`, so this function
|
|
101
101
|
* reads the counter at zero by construction, not by actual absence (see
|
|
102
|
-
* `.
|
|
102
|
+
* `.agents/docs/references/measured-foundations.md`). Whoever needs to
|
|
103
103
|
* assert an absence or count with precision must go through
|
|
104
104
|
* `snapshotAfterIdle` in `pepito/src/matchers.ts`, which closes that window
|
|
105
105
|
* with a two-observation stability condition; calling this function
|
package/dist/vue.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { render, type RenderResult } from 'vitest-browser-vue';
|
|
2
|
+
import type { MountOptions } from './mount-core';
|
|
3
|
+
/**
|
|
4
|
+
* Mounts a Vue component with `vitest-browser-vue`, optionally on a real
|
|
5
|
+
* document route and with test-specific MSW handlers.
|
|
6
|
+
*
|
|
7
|
+
* `path`, if given, has to be a same-origin URI starting with `/`: it's
|
|
8
|
+
* applied with `history.pushState` BEFORE render so the application's
|
|
9
|
+
* router reads it on mount. `setupNetwork()` restores the original URL
|
|
10
|
+
* in its `afterEach`.
|
|
11
|
+
*
|
|
12
|
+
* `network`, if given, is registered with `worker.use()` before render.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { mount } from '@yabbadabbadev/pepito/vue'
|
|
17
|
+
* import App from './App.vue'
|
|
18
|
+
*
|
|
19
|
+
* const screen = await mount(App, { path: '/products' })
|
|
20
|
+
* await expect.element(screen.getByText('Products')).toBeVisible()
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function mount(component: Parameters<typeof render>[0], options?: MountOptions): Promise<RenderResult<Record<string, unknown>>>;
|
package/dist/vue.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { render } from 'vitest-browser-vue';
|
|
2
|
+
import { mountCore } from './mount-core';
|
|
3
|
+
/**
|
|
4
|
+
* Mounts a Vue component with `vitest-browser-vue`, optionally on a real
|
|
5
|
+
* document route and with test-specific MSW handlers.
|
|
6
|
+
*
|
|
7
|
+
* `path`, if given, has to be a same-origin URI starting with `/`: it's
|
|
8
|
+
* applied with `history.pushState` BEFORE render so the application's
|
|
9
|
+
* router reads it on mount. `setupNetwork()` restores the original URL
|
|
10
|
+
* in its `afterEach`.
|
|
11
|
+
*
|
|
12
|
+
* `network`, if given, is registered with `worker.use()` before render.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { mount } from '@yabbadabbadev/pepito/vue'
|
|
17
|
+
* import App from './App.vue'
|
|
18
|
+
*
|
|
19
|
+
* const screen = await mount(App, { path: '/products' })
|
|
20
|
+
* await expect.element(screen.getByText('Products')).toBeVisible()
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export async function mount(component, options) {
|
|
24
|
+
return mountCore(component, (c) => render(c), options);
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yabbadabbadev/pepito",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Network test utilities for Vitest browser mode: application mounting and matchers over the traffic observed by MSW",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,18 @@
|
|
|
26
26
|
".": {
|
|
27
27
|
"types": "./dist/index.d.ts",
|
|
28
28
|
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./react": {
|
|
31
|
+
"types": "./dist/react.d.ts",
|
|
32
|
+
"default": "./dist/react.js"
|
|
33
|
+
},
|
|
34
|
+
"./vue": {
|
|
35
|
+
"types": "./dist/vue.d.ts",
|
|
36
|
+
"default": "./dist/vue.js"
|
|
37
|
+
},
|
|
38
|
+
"./svelte": {
|
|
39
|
+
"types": "./dist/svelte.d.ts",
|
|
40
|
+
"default": "./dist/svelte.js"
|
|
29
41
|
}
|
|
30
42
|
},
|
|
31
43
|
"files": [
|
|
@@ -51,13 +63,11 @@
|
|
|
51
63
|
},
|
|
52
64
|
"peerDependencies": {
|
|
53
65
|
"msw": "^2.15.0",
|
|
54
|
-
"
|
|
55
|
-
"react-dom": "^19.0.0",
|
|
56
|
-
"vitest": "^4.0.0",
|
|
57
|
-
"vitest-browser-react": "^2.0.0"
|
|
66
|
+
"vitest": "^4.0.0"
|
|
58
67
|
},
|
|
59
68
|
"devDependencies": {
|
|
60
69
|
"@eslint/js": "^9.39.5",
|
|
70
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
61
71
|
"@types/react": "^19.2.18",
|
|
62
72
|
"@types/react-dom": "^19.2.4",
|
|
63
73
|
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
|
@@ -80,9 +90,13 @@
|
|
|
80
90
|
"react": "^19.2.8",
|
|
81
91
|
"react-dom": "^19.2.8",
|
|
82
92
|
"react-router": "^8.3.0",
|
|
93
|
+
"svelte": "^5.57.0",
|
|
83
94
|
"typescript": "^6.0.3",
|
|
84
95
|
"vitest": "^4.1.10",
|
|
85
|
-
"vitest-browser-react": "^2.2.0"
|
|
96
|
+
"vitest-browser-react": "^2.2.0",
|
|
97
|
+
"vitest-browser-svelte": "^3.0.0",
|
|
98
|
+
"vitest-browser-vue": "^2.1.0",
|
|
99
|
+
"vue": "^3.5.42"
|
|
86
100
|
},
|
|
87
101
|
"msw": {
|
|
88
102
|
"workerDirectory": [
|
package/dist/mount.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from 'react';
|
|
2
|
-
import type { RequestHandler } from 'msw';
|
|
3
|
-
import { type RenderResult } from 'vitest-browser-react';
|
|
4
|
-
/** Options for {@link mount}. Both are optional: `mount(<App />)` alone just mounts. */
|
|
5
|
-
export interface MountOptions {
|
|
6
|
-
/**
|
|
7
|
-
* Same-origin URI starting with `/`, query and hash included (for example
|
|
8
|
-
* `/products?filter=bread#detail`). Applied with `history.pushState`
|
|
9
|
-
* before render so the application's router reads it on mount.
|
|
10
|
-
*/
|
|
11
|
-
path?: string;
|
|
12
|
-
/**
|
|
13
|
-
* MSW handlers of this test's own. Installed with `worker.use()` before
|
|
14
|
-
* render, so they take priority over the suite's for the same route, and
|
|
15
|
-
* `setupNetwork()` undoes them in its `afterEach`.
|
|
16
|
-
*/
|
|
17
|
-
network?: RequestHandler[];
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Mounts `ui` with `vitest-browser-react`, optionally on a real document
|
|
21
|
-
* route and with test-specific MSW handlers.
|
|
22
|
-
*
|
|
23
|
-
* `path`, if given, has to be a same-origin URI starting with `/`: it's
|
|
24
|
-
* applied with `history.pushState` BEFORE render because the application's
|
|
25
|
-
* `BrowserRouter` reads the URL on mount and only listens to `popstate`
|
|
26
|
-
* afterwards (measured — see
|
|
27
|
-
* `.claude/docs/references/measured-foundations.md`). The URI can carry
|
|
28
|
-
* query and hash: they flow through the router the same as the path.
|
|
29
|
-
* `setupNetwork()` restores the original URL in its `afterEach`, so every
|
|
30
|
-
* test starts from the same route regardless of what the previous one
|
|
31
|
-
* mounted.
|
|
32
|
-
*
|
|
33
|
-
* `network`, if given, is registered with `worker.use()` before render: its
|
|
34
|
-
* handlers take priority over the suite's for this request, with the same
|
|
35
|
-
* resolution rules as MSW.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```tsx
|
|
39
|
-
* import { mount } from '@yabbadabbadev/pepito'
|
|
40
|
-
*
|
|
41
|
-
* const screen = await mount(<App />, { path: '/products?filter=bread' })
|
|
42
|
-
* await expect.element(screen.getByText('filter: bread')).toBeVisible()
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
export declare function mount(ui: ReactElement, options?: MountOptions): Promise<RenderResult>;
|
package/dist/mount.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { render } from 'vitest-browser-react';
|
|
2
|
-
import { requireNetworkContext } from './network-singleton';
|
|
3
|
-
// The '/' prefix alone isn't enough: '//evil.com' is protocol-relative and
|
|
4
|
-
// WHATWG also treats '/\' as introducing an authority, so both would pass
|
|
5
|
-
// it and pushState would be the one throwing the raw SecurityError further
|
|
6
|
-
// down.
|
|
7
|
-
function isSameOriginPath(path) {
|
|
8
|
-
if (!path.startsWith('/'))
|
|
9
|
-
return false;
|
|
10
|
-
try {
|
|
11
|
-
return new URL(path, location.origin).origin === location.origin;
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Mounts `ui` with `vitest-browser-react`, optionally on a real document
|
|
19
|
-
* route and with test-specific MSW handlers.
|
|
20
|
-
*
|
|
21
|
-
* `path`, if given, has to be a same-origin URI starting with `/`: it's
|
|
22
|
-
* applied with `history.pushState` BEFORE render because the application's
|
|
23
|
-
* `BrowserRouter` reads the URL on mount and only listens to `popstate`
|
|
24
|
-
* afterwards (measured — see
|
|
25
|
-
* `.claude/docs/references/measured-foundations.md`). The URI can carry
|
|
26
|
-
* query and hash: they flow through the router the same as the path.
|
|
27
|
-
* `setupNetwork()` restores the original URL in its `afterEach`, so every
|
|
28
|
-
* test starts from the same route regardless of what the previous one
|
|
29
|
-
* mounted.
|
|
30
|
-
*
|
|
31
|
-
* `network`, if given, is registered with `worker.use()` before render: its
|
|
32
|
-
* handlers take priority over the suite's for this request, with the same
|
|
33
|
-
* resolution rules as MSW.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```tsx
|
|
37
|
-
* import { mount } from '@yabbadabbadev/pepito'
|
|
38
|
-
*
|
|
39
|
-
* const screen = await mount(<App />, { path: '/products?filter=bread' })
|
|
40
|
-
* await expect.element(screen.getByText('filter: bread')).toBeVisible()
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export async function mount(ui, options = {}) {
|
|
44
|
-
const { worker } = requireNetworkContext('mount');
|
|
45
|
-
const { path, network: testHandlers } = options;
|
|
46
|
-
if (path !== undefined && !isSameOriginPath(path)) {
|
|
47
|
-
throw new Error(`pepito: path must be a same-origin URI that starts with '/'; ` +
|
|
48
|
-
`received: ${path}. A different origin is mocked in the MSW ` +
|
|
49
|
-
`handlers, not in the mount.`);
|
|
50
|
-
}
|
|
51
|
-
// Before render: the app's router reads the URL on mount and only
|
|
52
|
-
// listens to popstate afterwards. See
|
|
53
|
-
// docs/knowledge/url-navegacion-browser-mode.md.
|
|
54
|
-
if (path !== undefined) {
|
|
55
|
-
history.pushState({}, '', path);
|
|
56
|
-
}
|
|
57
|
-
if (testHandlers !== undefined && testHandlers.length > 0) {
|
|
58
|
-
worker.use(...testHandlers);
|
|
59
|
-
}
|
|
60
|
-
return render(ui);
|
|
61
|
-
}
|