@brendanbank/atrium-test-utils 0.15.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/LICENCE.md +25 -0
- package/README.md +153 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +265 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/LICENCE.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Brendan Bank
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
this list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
18
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
19
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
20
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
21
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
22
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
23
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
24
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
25
|
+
POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# `@brendanbank/atrium-test-utils`
|
|
2
|
+
|
|
3
|
+
Vitest helpers for unit-testing [atrium](https://github.com/brendanbank/atrium)
|
|
4
|
+
host bundles. Replaces hand-rolled mocks of `window.__ATRIUM_REGISTRY__`
|
|
5
|
+
+ `window.React` + `useMe()` so a host can render permission-gated
|
|
6
|
+
components and fire synthetic SSE events without spinning up the SPA.
|
|
7
|
+
|
|
8
|
+
## Why
|
|
9
|
+
|
|
10
|
+
Before this package, casa-style host tests had to wire mocks like:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
(window as any).__ATRIUM_REGISTRY__ = {
|
|
15
|
+
registerHomeWidget: vi.fn(),
|
|
16
|
+
/* …seven more vi.fn()s… */
|
|
17
|
+
subscribeEvent: vi.fn(),
|
|
18
|
+
};
|
|
19
|
+
(window as any).React = require('react');
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
…and stub `useMe`'s underlying fetch separately. The seam is the same
|
|
24
|
+
in every host, so most teams skipped the unit tests and relied on
|
|
25
|
+
Playwright. This package gives them back.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
pnpm add -D @brendanbank/atrium-test-utils
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The package is published on **GitHub Packages**, same scope as the
|
|
34
|
+
other atrium SDK packages — see the host-bundle-utils
|
|
35
|
+
[`README`](../host-bundle-utils/README.md) for the `.npmrc` setup.
|
|
36
|
+
|
|
37
|
+
`vitest`, `@testing-library/react`, `react`, `react-dom`, and
|
|
38
|
+
`@tanstack/react-query` are peer deps — install them once in your
|
|
39
|
+
host's `frontend/package.json`; this package will not bundle a
|
|
40
|
+
second copy.
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import {
|
|
46
|
+
fireAtriumEvent,
|
|
47
|
+
mockAtriumRegistry,
|
|
48
|
+
renderWithAtrium,
|
|
49
|
+
type MockAtriumHandles,
|
|
50
|
+
type UserContext,
|
|
51
|
+
} from '@brendanbank/atrium-test-utils';
|
|
52
|
+
|
|
53
|
+
const ALICE: UserContext = {
|
|
54
|
+
id: 1, email: 'alice@example.com', full_name: 'Alice',
|
|
55
|
+
is_active: true,
|
|
56
|
+
roles: ['owner'],
|
|
57
|
+
permissions: ['commission.view.all'],
|
|
58
|
+
impersonating_from: null,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
let handles: MockAtriumHandles;
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
handles = mockAtriumRegistry({ me: ALICE });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
handles.cleanup();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('owners see the per-agent picker, not the self view', () => {
|
|
72
|
+
const { getByLabelText } = renderWithAtrium(<CommissionsPage />);
|
|
73
|
+
expect(getByLabelText('Agent')).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('the host bundle registers a home widget on import', async () => {
|
|
77
|
+
await import('../src/main');
|
|
78
|
+
expect(handles.homeWidgets).toHaveLength(1);
|
|
79
|
+
expect(handles.homeWidgets[0].key).toBe('commission-card');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('refetches bookings on booking.created', async () => {
|
|
83
|
+
renderWithAtrium(<BookingsPage />);
|
|
84
|
+
fireAtriumEvent('booking.created', { booking_id: 42 });
|
|
85
|
+
// …assert refetch fired
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
### `mockAtriumRegistry(options?)`
|
|
92
|
+
|
|
93
|
+
Installs a recording fake on `window.__ATRIUM_REGISTRY__`, exposes
|
|
94
|
+
React via `window.React`, and stubs `window.__atrium_i18n__` with
|
|
95
|
+
the bundled `common.*` keys. Returns handles:
|
|
96
|
+
|
|
97
|
+
| Field | Type | Description |
|
|
98
|
+
| ---------------------- | ----------------------------- | ----------------------------------------------------- |
|
|
99
|
+
| `registry` | `AtriumRegistry` | The fake (also at `window.__ATRIUM_REGISTRY__`). |
|
|
100
|
+
| `homeWidgets` | `HomeWidget[]` | Recorded `registerHomeWidget` calls in order. |
|
|
101
|
+
| `routes` | `RouteEntry[]` | Recorded `registerRoute` calls. |
|
|
102
|
+
| `navItems` | `NavItem[]` | Recorded `registerNavItem` calls. |
|
|
103
|
+
| `adminTabs` | `AdminTab[]` | Recorded `registerAdminTab` calls. |
|
|
104
|
+
| `profileItems` | `ProfileItem[]` | Recorded `registerProfileItem` calls. |
|
|
105
|
+
| `notificationKinds` | `NotificationKindRenderer[]` | Recorded `registerNotificationKind` calls. |
|
|
106
|
+
| `localeOverlays` | `LocaleOverlay[]` | Recorded `registerLocale` calls. |
|
|
107
|
+
| `reset()` | `() => void` | Drop registrations + subscribers, keep window mocks. |
|
|
108
|
+
| `cleanup()` | `() => void` | Restore pre-mock window state. Call in `afterEach`. |
|
|
109
|
+
|
|
110
|
+
Options:
|
|
111
|
+
|
|
112
|
+
| Option | Description |
|
|
113
|
+
| ------------------- | ---------------------------------------------------------------------------- |
|
|
114
|
+
| `me` | What `useMe()` resolves to. Pass `null` for signed-out. Default `null`. |
|
|
115
|
+
| `i18n.resources` | Locale → flat dot-path → string. Default: bundled English `common.*`. |
|
|
116
|
+
| `i18n.language` | Active locale. Default `'en'`. |
|
|
117
|
+
|
|
118
|
+
### `renderWithAtrium(ui, options?)`
|
|
119
|
+
|
|
120
|
+
`@testing-library/react`'s `render` pre-wired with a fresh
|
|
121
|
+
`QueryClient` and `<AtriumProvider>` pointed at the configured `me`.
|
|
122
|
+
Returns the standard `RenderResult` so existing assertions
|
|
123
|
+
(`getByRole`, `rerender`, `unmount`) work unchanged.
|
|
124
|
+
|
|
125
|
+
Options extend the standard `RenderOptions` with:
|
|
126
|
+
|
|
127
|
+
| Option | Description |
|
|
128
|
+
| ------------- | ------------------------------------------------------------------------ |
|
|
129
|
+
| `me` | Override `me` for this render. Falls back to the registry default. |
|
|
130
|
+
| `queryClient` | Share a client across renders so cache assertions survive remounts. |
|
|
131
|
+
| `wrap` | Additional wrapper component (e.g. `MantineProvider`, `RouterProvider`). |
|
|
132
|
+
|
|
133
|
+
### `fireAtriumEvent(kind, payload)`
|
|
134
|
+
|
|
135
|
+
Dispatches a synthetic SSE-style event to every handler the host
|
|
136
|
+
registered via the fake registry's `subscribeEvent`. No-op when
|
|
137
|
+
called outside a `mockAtriumRegistry` scope, so a forgotten setup
|
|
138
|
+
shows as an empty assertion rather than a crash.
|
|
139
|
+
|
|
140
|
+
## Versioning
|
|
141
|
+
|
|
142
|
+
Tracks `@brendanbank/atrium-host-bundle-utils`'s public API. When a
|
|
143
|
+
new registry slot lands in host-bundle-utils, the matching mock
|
|
144
|
+
shows up here in the next minor.
|
|
145
|
+
|
|
146
|
+
## See also
|
|
147
|
+
|
|
148
|
+
- [`@brendanbank/atrium-host-bundle-utils`](../host-bundle-utils/) — the
|
|
149
|
+
runtime helpers + hooks this package mocks.
|
|
150
|
+
- [`@brendanbank/atrium-host-types`](../host-types/) — typed registry
|
|
151
|
+
shapes.
|
|
152
|
+
- [`docs/published-images.md`](../../docs/published-images.md) — the
|
|
153
|
+
full host-extension contract.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest helpers for unit-testing atrium host bundles.
|
|
3
|
+
*
|
|
4
|
+
* Atrium loads a host bundle by setting ``window.__ATRIUM_REGISTRY__``
|
|
5
|
+
* and ``window.React`` and then ``await import()``-ing the bundle.
|
|
6
|
+
* Hosts that test components touching that surface have to fake the
|
|
7
|
+
* registry by hand — most casa tests today reach for Playwright
|
|
8
|
+
* instead, which is much slower and more brittle.
|
|
9
|
+
*
|
|
10
|
+
* This package installs the fakes once per test:
|
|
11
|
+
*
|
|
12
|
+
* - ``mockAtriumRegistry({ me })`` — installs a recording fake on
|
|
13
|
+
* ``window.__ATRIUM_REGISTRY__``, mirrors atrium's
|
|
14
|
+
* ``subscribeEvent`` semantics with an in-memory bus, exposes
|
|
15
|
+
* React via ``window.React`` (re-using the host's copy — atrium
|
|
16
|
+
* uses a single shared React in production), and stubs
|
|
17
|
+
* ``window.__atrium_i18n__`` so ``__atrium_t__`` calls resolve
|
|
18
|
+
* against an English fallback bundle. Returns handles so the test
|
|
19
|
+
* can assert against what the import-time side-effects registered.
|
|
20
|
+
* - ``renderWithAtrium(ui)`` — wraps ``@testing-library/react``'s
|
|
21
|
+
* ``render`` with a fresh ``QueryClient`` + ``<AtriumProvider>``
|
|
22
|
+
* pre-wired to return the configured ``me``. Returns the standard
|
|
23
|
+
* ``RenderResult`` so existing assertions work unchanged.
|
|
24
|
+
* - ``fireAtriumEvent(kind, payload)`` — dispatches a synthetic
|
|
25
|
+
* event the same way atrium's SSE stream would. Subscribers
|
|
26
|
+
* registered via the fake registry's ``subscribeEvent`` see it.
|
|
27
|
+
*
|
|
28
|
+
* Peer deps only — vitest, @testing-library/react, react,
|
|
29
|
+
* @tanstack/react-query are the host's, not bundled. The package
|
|
30
|
+
* provides the seam, not the framework.
|
|
31
|
+
*/
|
|
32
|
+
import { type ComponentType, type ReactElement, type ReactNode } from 'react';
|
|
33
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
34
|
+
import { type RenderOptions, type RenderResult } from '@testing-library/react';
|
|
35
|
+
import { type UserContext } from '@brendanbank/atrium-host-bundle-utils/react';
|
|
36
|
+
import type { AdminTab, AtriumEvent, AtriumEventHandler, AtriumRegistry, HomeWidget, LocaleOverlay, NavItem, NotificationKindRenderer, ProfileItem, RouteEntry } from '@brendanbank/atrium-host-types';
|
|
37
|
+
export type { AdminTab, AtriumEvent, AtriumEventHandler, AtriumRegistry, HomeWidget, LocaleOverlay, NavItem, NotificationKindRenderer, ProfileItem, RouteEntry, UserContext, };
|
|
38
|
+
/** Dispatch an atrium SSE-style event to every handler the host
|
|
39
|
+
* subscribed via the fake registry. No-op when called outside a
|
|
40
|
+
* ``mockAtriumRegistry`` scope so a test that forgets to call
|
|
41
|
+
* ``mockAtriumRegistry`` first surfaces as an empty assertion rather
|
|
42
|
+
* than a TypeError. */
|
|
43
|
+
export declare function fireAtriumEvent(kind: string, payload: Record<string, unknown>): void;
|
|
44
|
+
/** Recorded registrations + reset/cleanup hooks returned by
|
|
45
|
+
* ``mockAtriumRegistry``. Tests assert against the array fields and
|
|
46
|
+
* call ``cleanup()`` from ``afterEach`` to drop the
|
|
47
|
+
* window globals back to their pre-mock state. */
|
|
48
|
+
export interface MockAtriumHandles {
|
|
49
|
+
/** The fake registry installed on ``window.__ATRIUM_REGISTRY__``.
|
|
50
|
+
* Test bodies that want to call register methods directly (instead
|
|
51
|
+
* of through a host bundle's import-time side-effects) reach for
|
|
52
|
+
* this. */
|
|
53
|
+
registry: AtriumRegistry;
|
|
54
|
+
homeWidgets: HomeWidget[];
|
|
55
|
+
routes: RouteEntry[];
|
|
56
|
+
navItems: NavItem[];
|
|
57
|
+
adminTabs: AdminTab[];
|
|
58
|
+
profileItems: ProfileItem[];
|
|
59
|
+
notificationKinds: NotificationKindRenderer[];
|
|
60
|
+
localeOverlays: LocaleOverlay[];
|
|
61
|
+
/** Drop every recorded registration + every event subscriber.
|
|
62
|
+
* Useful between tests inside the same ``describe`` that want to
|
|
63
|
+
* share the rest of the mock context. */
|
|
64
|
+
reset(): void;
|
|
65
|
+
/** Restore the pre-mock window state. Call from ``afterEach`` so
|
|
66
|
+
* the next test's ``mockAtriumRegistry`` starts from a clean
|
|
67
|
+
* baseline. */
|
|
68
|
+
cleanup(): void;
|
|
69
|
+
}
|
|
70
|
+
export interface MockAtriumOptions {
|
|
71
|
+
/** What ``useMe()`` resolves to. Pass ``null`` to simulate a
|
|
72
|
+
* signed-out user (atrium responded 401/403). Defaults to
|
|
73
|
+
* ``null``. */
|
|
74
|
+
me?: UserContext | null;
|
|
75
|
+
/** Locale resources the fake ``__atrium_t__`` resolves against.
|
|
76
|
+
* Keys are flat dot-paths (``'common.save'``); the outer key is
|
|
77
|
+
* the locale code. Defaults to a minimal English bundle covering
|
|
78
|
+
* the keys atrium ships in ``common.*``. Pass an empty object to
|
|
79
|
+
* exercise the missing-key fallback. */
|
|
80
|
+
i18n?: {
|
|
81
|
+
resources?: Record<string, Record<string, string>>;
|
|
82
|
+
language?: string;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Install a recording fake registry on ``window`` for the duration of
|
|
86
|
+
* a test. The handles returned record every register* call so tests
|
|
87
|
+
* can assert that a host bundle wired up the slots it claims to.
|
|
88
|
+
*
|
|
89
|
+
* ```ts
|
|
90
|
+
* let handles: MockAtriumHandles;
|
|
91
|
+
* beforeEach(() => {
|
|
92
|
+
* handles = mockAtriumRegistry({
|
|
93
|
+
* me: {
|
|
94
|
+
* id: 1, email: 'alice@example.com', full_name: 'Alice',
|
|
95
|
+
* is_active: true, roles: ['admin'],
|
|
96
|
+
* permissions: ['hello.toggle'], impersonating_from: null,
|
|
97
|
+
* },
|
|
98
|
+
* });
|
|
99
|
+
* });
|
|
100
|
+
* afterEach(() => handles.cleanup());
|
|
101
|
+
*
|
|
102
|
+
* test('host bundle registers a home widget', async () => {
|
|
103
|
+
* await import('../src/main');
|
|
104
|
+
* expect(handles.homeWidgets).toHaveLength(1);
|
|
105
|
+
* expect(handles.homeWidgets[0].key).toBe('hello-world');
|
|
106
|
+
* });
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* Side effects:
|
|
110
|
+
* - ``window.__ATRIUM_REGISTRY__`` is replaced with the fake.
|
|
111
|
+
* - ``window.React`` is set to the package's React copy so
|
|
112
|
+
* ``makeWrapperElement(...)`` calls resolve. Tests that need a
|
|
113
|
+
* specific React identity can override after the call.
|
|
114
|
+
* - ``window.__atrium_i18n__`` is set to a fake i18n with the
|
|
115
|
+
* bundled English ``common.*`` strings (override via the
|
|
116
|
+
* ``i18n`` option). */
|
|
117
|
+
export declare function mockAtriumRegistry(options?: MockAtriumOptions): MockAtriumHandles;
|
|
118
|
+
export interface RenderWithAtriumOptions extends RenderOptions {
|
|
119
|
+
/** Override ``me`` for this single render. Useful for
|
|
120
|
+
* parameterised tests that vary the signed-in user without
|
|
121
|
+
* re-installing the registry. Falls back to the value from
|
|
122
|
+
* ``mockAtriumRegistry({ me })``. */
|
|
123
|
+
me?: UserContext | null;
|
|
124
|
+
/** Provide your own QueryClient — useful if the test asserts
|
|
125
|
+
* against cached data or invalidations. Defaults to a fresh client
|
|
126
|
+
* with retries disabled so a 401 surfaces immediately. */
|
|
127
|
+
queryClient?: QueryClient;
|
|
128
|
+
/** Wrap children in additional providers (MantineProvider,
|
|
129
|
+
* RouterProvider, etc.). Default: identity. */
|
|
130
|
+
wrap?: ComponentType<{
|
|
131
|
+
children: ReactNode;
|
|
132
|
+
}>;
|
|
133
|
+
}
|
|
134
|
+
/** ``@testing-library/react``'s ``render`` pre-wired with a fresh
|
|
135
|
+
* QueryClient and ``<AtriumProvider>`` pointed at the configured
|
|
136
|
+
* ``me``. Returns the standard ``RenderResult`` so existing
|
|
137
|
+
* assertions (``getByRole``, ``rerender``, ``unmount``) work
|
|
138
|
+
* unchanged.
|
|
139
|
+
*
|
|
140
|
+
* ```tsx
|
|
141
|
+
* test('owners see the per-agent picker', () => {
|
|
142
|
+
* const { getByLabelText } = renderWithAtrium(<CommissionsPage />);
|
|
143
|
+
* expect(getByLabelText('Agent')).toBeInTheDocument();
|
|
144
|
+
* });
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* Pass ``me`` to override the value from ``mockAtriumRegistry`` for
|
|
148
|
+
* a single render — handy in parameterised tests. Pass
|
|
149
|
+
* ``queryClient`` to share a client across renders so cache
|
|
150
|
+
* assertions survive the second mount. */
|
|
151
|
+
export declare function renderWithAtrium(ui: ReactElement, options?: RenderWithAtriumOptions): RenderResult;
|
|
152
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EACL,WAAW,EAEZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAU,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEvF,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EACV,QAAQ,EACR,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,aAAa,EACb,OAAO,EACP,wBAAwB,EACxB,WAAW,EACX,UAAU,EACX,MAAM,gCAAgC,CAAC;AAExC,YAAY,EACV,QAAQ,EACR,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,aAAa,EACb,OAAO,EACP,wBAAwB,EACxB,WAAW,EACX,UAAU,EACV,WAAW,GACZ,CAAC;AAsDF;;;;wBAIwB;AACxB,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAWN;AAMD;;;mDAGmD;AACnD,MAAM,WAAW,iBAAiB;IAChC;;;gBAGY;IACZ,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,iBAAiB,EAAE,wBAAwB,EAAE,CAAC;IAC9C,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC;;8CAE0C;IAC1C,KAAK,IAAI,IAAI,CAAC;IACd;;oBAEgB;IAChB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC;;oBAEgB;IAChB,EAAE,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACxB;;;;6CAIyC;IACzC,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AA8DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BA+B2B;AAC3B,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,iBAAsB,GAC9B,iBAAiB,CAqEnB;AAQD,MAAM,WAAW,uBAAwB,SAAQ,aAAa;IAC5D;;;0CAGsC;IACtC,EAAE,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACxB;;+DAE2D;IAC3D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;oDACgD;IAChD,IAAI,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC;CAC/C;AAQD;;;;;;;;;;;;;;;;2CAgB2C;AAC3C,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,YAAY,EAChB,OAAO,GAAE,uBAA4B,GACpC,YAAY,CAgBd"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// Copyright (c) 2026 Brendan Bank
|
|
2
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
|
+
/**
|
|
4
|
+
* Vitest helpers for unit-testing atrium host bundles.
|
|
5
|
+
*
|
|
6
|
+
* Atrium loads a host bundle by setting ``window.__ATRIUM_REGISTRY__``
|
|
7
|
+
* and ``window.React`` and then ``await import()``-ing the bundle.
|
|
8
|
+
* Hosts that test components touching that surface have to fake the
|
|
9
|
+
* registry by hand — most casa tests today reach for Playwright
|
|
10
|
+
* instead, which is much slower and more brittle.
|
|
11
|
+
*
|
|
12
|
+
* This package installs the fakes once per test:
|
|
13
|
+
*
|
|
14
|
+
* - ``mockAtriumRegistry({ me })`` — installs a recording fake on
|
|
15
|
+
* ``window.__ATRIUM_REGISTRY__``, mirrors atrium's
|
|
16
|
+
* ``subscribeEvent`` semantics with an in-memory bus, exposes
|
|
17
|
+
* React via ``window.React`` (re-using the host's copy — atrium
|
|
18
|
+
* uses a single shared React in production), and stubs
|
|
19
|
+
* ``window.__atrium_i18n__`` so ``__atrium_t__`` calls resolve
|
|
20
|
+
* against an English fallback bundle. Returns handles so the test
|
|
21
|
+
* can assert against what the import-time side-effects registered.
|
|
22
|
+
* - ``renderWithAtrium(ui)`` — wraps ``@testing-library/react``'s
|
|
23
|
+
* ``render`` with a fresh ``QueryClient`` + ``<AtriumProvider>``
|
|
24
|
+
* pre-wired to return the configured ``me``. Returns the standard
|
|
25
|
+
* ``RenderResult`` so existing assertions work unchanged.
|
|
26
|
+
* - ``fireAtriumEvent(kind, payload)`` — dispatches a synthetic
|
|
27
|
+
* event the same way atrium's SSE stream would. Subscribers
|
|
28
|
+
* registered via the fake registry's ``subscribeEvent`` see it.
|
|
29
|
+
*
|
|
30
|
+
* Peer deps only — vitest, @testing-library/react, react,
|
|
31
|
+
* @tanstack/react-query are the host's, not bundled. The package
|
|
32
|
+
* provides the seam, not the framework.
|
|
33
|
+
*/
|
|
34
|
+
import { createElement, } from 'react';
|
|
35
|
+
import * as ReactNS from 'react';
|
|
36
|
+
import { QueryClient, QueryClientProvider, } from '@tanstack/react-query';
|
|
37
|
+
import { render } from '@testing-library/react';
|
|
38
|
+
import { AtriumProvider, } from '@brendanbank/atrium-host-bundle-utils/react';
|
|
39
|
+
function makeFakeI18n(resources, language) {
|
|
40
|
+
return {
|
|
41
|
+
language,
|
|
42
|
+
t(key, options) {
|
|
43
|
+
const langs = [language, 'en'];
|
|
44
|
+
for (const l of langs) {
|
|
45
|
+
const hit = resources[l]?.[key];
|
|
46
|
+
if (typeof hit === 'string') {
|
|
47
|
+
return interpolate(hit, options);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const def = options?.defaultValue;
|
|
51
|
+
return typeof def === 'string' ? def : key;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function interpolate(s, vars) {
|
|
56
|
+
if (!vars)
|
|
57
|
+
return s;
|
|
58
|
+
return s.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
59
|
+
if (key === 'defaultValue')
|
|
60
|
+
return match;
|
|
61
|
+
const v = vars[key];
|
|
62
|
+
return v == null ? match : String(v);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
let activeSubscribers = null;
|
|
66
|
+
/** Dispatch an atrium SSE-style event to every handler the host
|
|
67
|
+
* subscribed via the fake registry. No-op when called outside a
|
|
68
|
+
* ``mockAtriumRegistry`` scope so a test that forgets to call
|
|
69
|
+
* ``mockAtriumRegistry`` first surfaces as an empty assertion rather
|
|
70
|
+
* than a TypeError. */
|
|
71
|
+
export function fireAtriumEvent(kind, payload) {
|
|
72
|
+
const subs = activeSubscribers;
|
|
73
|
+
if (!subs)
|
|
74
|
+
return;
|
|
75
|
+
const handlers = subs.get(kind);
|
|
76
|
+
if (!handlers || handlers.size === 0)
|
|
77
|
+
return;
|
|
78
|
+
const event = { kind, payload };
|
|
79
|
+
// Snapshot so a handler that unsubscribes mid-fire doesn't skip a
|
|
80
|
+
// sibling handler that's later in iteration order.
|
|
81
|
+
for (const handler of [...handlers]) {
|
|
82
|
+
handler(event);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const DEFAULT_I18N_EN = {
|
|
86
|
+
'common.save': 'Save',
|
|
87
|
+
'common.cancel': 'Cancel',
|
|
88
|
+
'common.delete': 'Delete',
|
|
89
|
+
'common.edit': 'Edit',
|
|
90
|
+
'common.new': 'New',
|
|
91
|
+
'common.close': 'Close',
|
|
92
|
+
'common.confirm': 'Confirm',
|
|
93
|
+
'common.back': 'Back',
|
|
94
|
+
'common.loading': 'Loading…',
|
|
95
|
+
'common.empty': 'Nothing here yet.',
|
|
96
|
+
'common.error': 'Something went wrong.',
|
|
97
|
+
'common.search': 'Search',
|
|
98
|
+
'common.required': 'Required',
|
|
99
|
+
'common.language': 'Language',
|
|
100
|
+
};
|
|
101
|
+
function buildFakeRegistry(handles, subs) {
|
|
102
|
+
return {
|
|
103
|
+
registerHomeWidget(widget) {
|
|
104
|
+
handles.homeWidgets.push(widget);
|
|
105
|
+
},
|
|
106
|
+
registerRoute(route) {
|
|
107
|
+
handles.routes.push(route);
|
|
108
|
+
},
|
|
109
|
+
registerNavItem(item) {
|
|
110
|
+
handles.navItems.push(item);
|
|
111
|
+
},
|
|
112
|
+
registerAdminTab(tab) {
|
|
113
|
+
handles.adminTabs.push(tab);
|
|
114
|
+
},
|
|
115
|
+
registerProfileItem(item) {
|
|
116
|
+
handles.profileItems.push(item);
|
|
117
|
+
},
|
|
118
|
+
registerNotificationKind(renderer) {
|
|
119
|
+
handles.notificationKinds.push(renderer);
|
|
120
|
+
},
|
|
121
|
+
registerLocale(overlay) {
|
|
122
|
+
handles.localeOverlays.push(overlay);
|
|
123
|
+
},
|
|
124
|
+
subscribeEvent(kind, handler) {
|
|
125
|
+
let set = subs.get(kind);
|
|
126
|
+
if (!set) {
|
|
127
|
+
set = new Set();
|
|
128
|
+
subs.set(kind, set);
|
|
129
|
+
}
|
|
130
|
+
set.add(handler);
|
|
131
|
+
return () => {
|
|
132
|
+
set?.delete(handler);
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Install a recording fake registry on ``window`` for the duration of
|
|
138
|
+
* a test. The handles returned record every register* call so tests
|
|
139
|
+
* can assert that a host bundle wired up the slots it claims to.
|
|
140
|
+
*
|
|
141
|
+
* ```ts
|
|
142
|
+
* let handles: MockAtriumHandles;
|
|
143
|
+
* beforeEach(() => {
|
|
144
|
+
* handles = mockAtriumRegistry({
|
|
145
|
+
* me: {
|
|
146
|
+
* id: 1, email: 'alice@example.com', full_name: 'Alice',
|
|
147
|
+
* is_active: true, roles: ['admin'],
|
|
148
|
+
* permissions: ['hello.toggle'], impersonating_from: null,
|
|
149
|
+
* },
|
|
150
|
+
* });
|
|
151
|
+
* });
|
|
152
|
+
* afterEach(() => handles.cleanup());
|
|
153
|
+
*
|
|
154
|
+
* test('host bundle registers a home widget', async () => {
|
|
155
|
+
* await import('../src/main');
|
|
156
|
+
* expect(handles.homeWidgets).toHaveLength(1);
|
|
157
|
+
* expect(handles.homeWidgets[0].key).toBe('hello-world');
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* Side effects:
|
|
162
|
+
* - ``window.__ATRIUM_REGISTRY__`` is replaced with the fake.
|
|
163
|
+
* - ``window.React`` is set to the package's React copy so
|
|
164
|
+
* ``makeWrapperElement(...)`` calls resolve. Tests that need a
|
|
165
|
+
* specific React identity can override after the call.
|
|
166
|
+
* - ``window.__atrium_i18n__`` is set to a fake i18n with the
|
|
167
|
+
* bundled English ``common.*`` strings (override via the
|
|
168
|
+
* ``i18n`` option). */
|
|
169
|
+
export function mockAtriumRegistry(options = {}) {
|
|
170
|
+
if (typeof window === 'undefined') {
|
|
171
|
+
throw new Error('[atrium-test-utils] mockAtriumRegistry requires a jsdom-style ' +
|
|
172
|
+
'window — set vitest.environment to "jsdom" in vitest.config.ts');
|
|
173
|
+
}
|
|
174
|
+
const w = window;
|
|
175
|
+
const saved = {
|
|
176
|
+
registry: w.__ATRIUM_REGISTRY__,
|
|
177
|
+
React: w.React,
|
|
178
|
+
i18n: w.__atrium_i18n__,
|
|
179
|
+
};
|
|
180
|
+
const subs = new Map();
|
|
181
|
+
const handles = {
|
|
182
|
+
registry: undefined,
|
|
183
|
+
homeWidgets: [],
|
|
184
|
+
routes: [],
|
|
185
|
+
navItems: [],
|
|
186
|
+
adminTabs: [],
|
|
187
|
+
profileItems: [],
|
|
188
|
+
notificationKinds: [],
|
|
189
|
+
localeOverlays: [],
|
|
190
|
+
reset() {
|
|
191
|
+
handles.homeWidgets.length = 0;
|
|
192
|
+
handles.routes.length = 0;
|
|
193
|
+
handles.navItems.length = 0;
|
|
194
|
+
handles.adminTabs.length = 0;
|
|
195
|
+
handles.profileItems.length = 0;
|
|
196
|
+
handles.notificationKinds.length = 0;
|
|
197
|
+
handles.localeOverlays.length = 0;
|
|
198
|
+
subs.clear();
|
|
199
|
+
},
|
|
200
|
+
cleanup() {
|
|
201
|
+
w.__ATRIUM_REGISTRY__ = saved.registry;
|
|
202
|
+
w.React = saved.React;
|
|
203
|
+
w.__atrium_i18n__ = saved.i18n;
|
|
204
|
+
if (activeSubscribers === subs) {
|
|
205
|
+
activeSubscribers = null;
|
|
206
|
+
}
|
|
207
|
+
subs.clear();
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
handles.registry = buildFakeRegistry(handles, subs);
|
|
211
|
+
w.__ATRIUM_REGISTRY__ = handles.registry;
|
|
212
|
+
w.React = ReactNS;
|
|
213
|
+
const i18nResources = options.i18n?.resources ?? { en: DEFAULT_I18N_EN };
|
|
214
|
+
const language = options.i18n?.language ?? 'en';
|
|
215
|
+
w.__atrium_i18n__ = makeFakeI18n(i18nResources, language);
|
|
216
|
+
// Stash the configured ``me`` on a module-level slot so
|
|
217
|
+
// ``renderWithAtrium`` can pass it through to AtriumProvider's
|
|
218
|
+
// ``fetchUserContext`` override without each call site re-passing
|
|
219
|
+
// it. Cleared by ``cleanup()`` via the activeSubscribers reset.
|
|
220
|
+
configuredMe = options.me ?? null;
|
|
221
|
+
activeSubscribers = subs;
|
|
222
|
+
return handles;
|
|
223
|
+
}
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// renderWithAtrium
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
let configuredMe = null;
|
|
228
|
+
function makeTestQueryClient() {
|
|
229
|
+
return new QueryClient({
|
|
230
|
+
defaultOptions: { queries: { retry: false } },
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/** ``@testing-library/react``'s ``render`` pre-wired with a fresh
|
|
234
|
+
* QueryClient and ``<AtriumProvider>`` pointed at the configured
|
|
235
|
+
* ``me``. Returns the standard ``RenderResult`` so existing
|
|
236
|
+
* assertions (``getByRole``, ``rerender``, ``unmount``) work
|
|
237
|
+
* unchanged.
|
|
238
|
+
*
|
|
239
|
+
* ```tsx
|
|
240
|
+
* test('owners see the per-agent picker', () => {
|
|
241
|
+
* const { getByLabelText } = renderWithAtrium(<CommissionsPage />);
|
|
242
|
+
* expect(getByLabelText('Agent')).toBeInTheDocument();
|
|
243
|
+
* });
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* Pass ``me`` to override the value from ``mockAtriumRegistry`` for
|
|
247
|
+
* a single render — handy in parameterised tests. Pass
|
|
248
|
+
* ``queryClient`` to share a client across renders so cache
|
|
249
|
+
* assertions survive the second mount. */
|
|
250
|
+
export function renderWithAtrium(ui, options = {}) {
|
|
251
|
+
const { me, queryClient, wrap, ...rest } = options;
|
|
252
|
+
const client = queryClient ?? makeTestQueryClient();
|
|
253
|
+
const meValue = me === undefined ? configuredMe : me;
|
|
254
|
+
const Wrap = wrap;
|
|
255
|
+
function Wrapper({ children }) {
|
|
256
|
+
const inner = createElement(AtriumProvider, {
|
|
257
|
+
fetchUserContext: async () => meValue,
|
|
258
|
+
children,
|
|
259
|
+
});
|
|
260
|
+
const wrapped = Wrap ? createElement(Wrap, { children: inner }) : inner;
|
|
261
|
+
return createElement(QueryClientProvider, { client, children: wrapped });
|
|
262
|
+
}
|
|
263
|
+
return render(ui, { wrapper: Wrapper, ...rest });
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,wCAAwC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EACL,aAAa,GAId,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,OAAO,MAAM,OAAO,CAAC;AACjC,OAAO,EACL,WAAW,EACX,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAyC,MAAM,wBAAwB,CAAC;AAEvF,OAAO,EACL,cAAc,GAEf,MAAM,6CAA6C,CAAC;AAwCrD,SAAS,YAAY,CACnB,SAAiD,EACjD,QAAgB;IAEhB,OAAO;QACL,QAAQ;QACR,CAAC,CAAC,GAAG,EAAE,OAAO;YACZ,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC/B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;gBAChC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;oBAC5B,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBACnC,CAAC;YACH,CAAC;YACD,MAAM,GAAG,GAAG,OAAO,EAAE,YAAY,CAAC;YAClC,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAC7C,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAClB,CAAS,EACT,IAA0D;IAE1D,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC;IACpB,OAAO,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAChD,IAAI,GAAG,KAAK,cAAc;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC;AAQD,IAAI,iBAAiB,GAAuB,IAAI,CAAC;AAEjD;;;;wBAIwB;AACxB,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,OAAgC;IAEhC,MAAM,IAAI,GAAG,iBAAiB,CAAC;IAC/B,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAC7C,MAAM,KAAK,GAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC7C,kEAAkE;IAClE,mDAAmD;IACnD,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC;QACpC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;AACH,CAAC;AAuDD,MAAM,eAAe,GAA2B;IAC9C,aAAa,EAAE,MAAM;IACrB,eAAe,EAAE,QAAQ;IACzB,eAAe,EAAE,QAAQ;IACzB,aAAa,EAAE,MAAM;IACrB,YAAY,EAAE,KAAK;IACnB,cAAc,EAAE,OAAO;IACvB,gBAAgB,EAAE,SAAS;IAC3B,aAAa,EAAE,MAAM;IACrB,gBAAgB,EAAE,UAAU;IAC5B,cAAc,EAAE,mBAAmB;IACnC,cAAc,EAAE,uBAAuB;IACvC,eAAe,EAAE,QAAQ;IACzB,iBAAiB,EAAE,UAAU;IAC7B,iBAAiB,EAAE,UAAU;CAC9B,CAAC;AAEF,SAAS,iBAAiB,CAAC,OAA0B,EAAE,IAAiB;IACtE,OAAO;QACL,kBAAkB,CAAC,MAAM;YACvB,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,aAAa,CAAC,KAAK;YACjB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QACD,eAAe,CAAC,IAAI;YAClB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,gBAAgB,CAAC,GAAG;YAClB,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;QACD,mBAAmB,CAAC,IAAI;YACtB,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QACD,wBAAwB,CAAC,QAAQ;YAC/B,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3C,CAAC;QACD,cAAc,CAAC,OAAO;YACpB,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;QACD,cAAc,CAAC,IAAI,EAAE,OAAO;YAC1B,IAAI,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACzB,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;gBAChB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACtB,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACjB,OAAO,GAAG,EAAE;gBACV,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BA+B2B;AAC3B,MAAM,UAAU,kBAAkB,CAChC,UAA6B,EAAE;IAE/B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CACb,gEAAgE;YAC9D,gEAAgE,CACnE,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,GAAG,MAIT,CAAC;IAEF,MAAM,KAAK,GAAiB;QAC1B,QAAQ,EAAE,CAAC,CAAC,mBAAmB;QAC/B,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,IAAI,EAAE,CAAC,CAAC,eAAe;KACxB,CAAC;IAEF,MAAM,IAAI,GAAgB,IAAI,GAAG,EAAE,CAAC;IACpC,MAAM,OAAO,GAAsB;QACjC,QAAQ,EAAE,SAAsC;QAChD,WAAW,EAAE,EAAE;QACf,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE,EAAE;QACb,YAAY,EAAE,EAAE;QAChB,iBAAiB,EAAE,EAAE;QACrB,cAAc,EAAE,EAAE;QAClB,KAAK;YACH,OAAO,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;YAC/B,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC5B,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;YAC7B,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;YAChC,OAAO,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC;YACrC,OAAO,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;YAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;QACD,OAAO;YACL,CAAC,CAAC,mBAAmB,GAAG,KAAK,CAAC,QAAQ,CAAC;YACvC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;YACtB,CAAC,CAAC,eAAe,GAAG,KAAK,CAAC,IAAI,CAAC;YAC/B,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;gBAC/B,iBAAiB,GAAG,IAAI,CAAC;YAC3B,CAAC;YACD,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;KACF,CAAC;IAEF,OAAO,CAAC,QAAQ,GAAG,iBAAiB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAEpD,CAAC,CAAC,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;IACzC,CAAC,CAAC,KAAK,GAAG,OAAO,CAAC;IAElB,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,EAAE,SAAS,IAAI,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC;IACzE,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,IAAI,IAAI,CAAC;IAChD,CAAC,CAAC,eAAe,GAAG,YAAY,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAE1D,wDAAwD;IACxD,+DAA+D;IAC/D,kEAAkE;IAClE,gEAAgE;IAChE,YAAY,GAAG,OAAO,CAAC,EAAE,IAAI,IAAI,CAAC;IAElC,iBAAiB,GAAG,IAAI,CAAC;IAEzB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,IAAI,YAAY,GAAuB,IAAI,CAAC;AAiB5C,SAAS,mBAAmB;IAC1B,OAAO,IAAI,WAAW,CAAC;QACrB,cAAc,EAAE,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;KAC9C,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;2CAgB2C;AAC3C,MAAM,UAAU,gBAAgB,CAC9B,EAAgB,EAChB,UAAmC,EAAE;IAErC,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACnD,MAAM,MAAM,GAAG,WAAW,IAAI,mBAAmB,EAAE,CAAC;IACpD,MAAM,OAAO,GAAG,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IACrD,MAAM,IAAI,GAAG,IAAI,CAAC;IAElB,SAAS,OAAO,CAAC,EAAE,QAAQ,EAA2B;QACpD,MAAM,KAAK,GAAG,aAAa,CAAC,cAAc,EAAE;YAC1C,gBAAgB,EAAE,KAAK,IAAI,EAAE,CAAC,OAAO;YACrC,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACxE,OAAO,aAAa,CAAC,mBAAmB,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;AACnD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brendanbank/atrium-test-utils",
|
|
3
|
+
"version": "0.15.0",
|
|
4
|
+
"description": "Vitest helpers for unit-testing atrium host bundles. Installs a fake __ATRIUM_REGISTRY__ + window.React, wires a working QueryClient, and exposes a synthetic event bus that mirrors atrium's subscribeEvent semantics — so a host can render permission-gated components and fire SSE events from a test without standing up the SPA.",
|
|
5
|
+
"license": "BSD-2-Clause",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@brendanbank/atrium-host-bundle-utils": "0.15.0",
|
|
21
|
+
"@brendanbank/atrium-host-types": "0.15.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@tanstack/react-query": "^5.0.0",
|
|
25
|
+
"@testing-library/react": "^16.0.0",
|
|
26
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
27
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
28
|
+
"vitest": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@tanstack/react-query": "^5.100.5",
|
|
32
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
33
|
+
"@testing-library/react": "^16.3.2",
|
|
34
|
+
"@types/node": "^25.6.0",
|
|
35
|
+
"@types/react": "^19.2.14",
|
|
36
|
+
"@types/react-dom": "^19.2.3",
|
|
37
|
+
"jsdom": "^29.0.2",
|
|
38
|
+
"react": "^19.2.5",
|
|
39
|
+
"react-dom": "^19.2.5",
|
|
40
|
+
"typescript": "^6.0.3",
|
|
41
|
+
"vitest": "^4.1.5"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/brendanbank/atrium.git",
|
|
49
|
+
"directory": "packages/test-utils"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsc -p tsconfig.json",
|
|
53
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"clean": "rm -rf dist"
|
|
57
|
+
}
|
|
58
|
+
}
|