@uniflowed/test 0.0.0-alpha.8 → 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/app-browser.js +6 -0
- package/app.js +175 -0
- package/browser-worker.js +334 -0
- package/browser.js +59 -0
- package/bun/index.js +305 -0
- package/in-source.js +169 -0
- package/index.js +11 -1
- package/internal/axe.js +362 -0
- package/internal/browser/cdp.js +364 -0
- package/internal/browser/node.js +375 -0
- package/internal/browser/page-transport.js +36 -0
- package/internal/browser/page.js +258 -0
- package/internal/browser/screenshots.js +102 -0
- package/internal/browser/server.js +893 -0
- package/internal/browser/transport.js +2 -0
- package/internal/expect.js +432 -80
- package/internal/isolation.js +231 -0
- package/internal/namespace.js +22 -10
- package/internal/native-globals.js +14 -0
- package/internal/native-host.js +97 -0
- package/internal/output.js +25 -7
- package/internal/registry.js +54 -3
- package/internal/run.js +130 -11
- package/internal/timers.js +2 -2
- package/package.json +41 -5
- package/worker.js +98 -10
package/bun/index.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// @noflow
|
|
2
|
+
//
|
|
3
|
+
// Plain JavaScript: `bun:test` has no Flow library definition, and the only
|
|
4
|
+
// thing that ever imports this is a `bun test` that `uf test` started.
|
|
5
|
+
//
|
|
6
|
+
// `@uniflowed/test`, when the suite is run by `bun test`.
|
|
7
|
+
//
|
|
8
|
+
// A project that writes `test: { runner: "bun" }` in `uf.config.js` has its
|
|
9
|
+
// suite run by `bun test` instead of uf's own runner, and its test files do not
|
|
10
|
+
// change: they still import `@uniflowed/test`. `uf test` starts Bun with the
|
|
11
|
+
// `uniflowed-bun-test` export condition, and this package's `exports` sends
|
|
12
|
+
// `@uniflowed/test` here under that condition and to `../index.js` under every
|
|
13
|
+
// other. uf's own runner never sets it, on Node or on Bun, so the only process
|
|
14
|
+
// that meets this file is one `uf test` started to hand a suite to Bun.
|
|
15
|
+
//
|
|
16
|
+
// # Why a condition rather than a plugin
|
|
17
|
+
//
|
|
18
|
+
// A `Bun.plugin` `onResolve` is not consulted for a bare specifier that
|
|
19
|
+
// resolves to an installed package, and `@uniflowed/test` is installed in every
|
|
20
|
+
// project that uses it. Tried on Bun 1.3.13, the test file imported uf's real
|
|
21
|
+
// API, registered its cases where Bun could not see them, and `bun test`
|
|
22
|
+
// printed "Ran 0 tests across 1 file" and exited 0 — a green run over a suite
|
|
23
|
+
// that ran nothing. Resolution is the one place the choice is made before a
|
|
24
|
+
// test file's imports run. See ubugeeei-prod/uf#942.
|
|
25
|
+
//
|
|
26
|
+
// # What maps, and what refuses
|
|
27
|
+
//
|
|
28
|
+
// The registration API, the hooks and `expect` are Bun's own, with the
|
|
29
|
+
// modifiers both runners give them: `.only`, `.skip`, `.todo` and `.each`
|
|
30
|
+
// (whose `%s %j %d %i` placeholders both runners format). `fn`, `spyOn`, and
|
|
31
|
+
// the `uft` members Bun has an equivalent for — the fake clock and the mock
|
|
32
|
+
// resets — are Bun's. uf's host-agnostic helpers are uf's: `uft.waitFor` and
|
|
33
|
+
// `uft.waitUntil` poll with `setTimeout` under either runner, `uft.mocked` is
|
|
34
|
+
// the identity function, and `equals` and `render` are pure.
|
|
35
|
+
//
|
|
36
|
+
// Everything else raises `UnsupportedError` at the moment it is used, naming
|
|
37
|
+
// what was called and why there is nothing here to give it — never an
|
|
38
|
+
// approximation that would report a different result:
|
|
39
|
+
//
|
|
40
|
+
// * `it.skipBecause`: Bun's skip carries no reason into its report, and a skip
|
|
41
|
+
// whose reason quietly disappears is what `skipBecause` exists to prevent.
|
|
42
|
+
// * uf's DOM matchers and `toHaveNoAxeViolations`: Bun's `expect` has none.
|
|
43
|
+
// * `uft.stubEnv`, `uft.stubGlobal` and their `unstubAll` pairs: uf puts a stub
|
|
44
|
+
// back before the next file, and `bun test` runs every file in one process
|
|
45
|
+
// where nothing would.
|
|
46
|
+
// * Module mocking: Bun's `mock.module` rewrites modules that have already
|
|
47
|
+
// imported the real one, which is a different promise from `uft.mock`'s.
|
|
48
|
+
// * `uft.advanceTimersByTimeAsync`: Bun's fake clock has no asynchronous
|
|
49
|
+
// advance, and flushing microtasks between timers is the whole of what it
|
|
50
|
+
// promises.
|
|
51
|
+
// * `AssertionError` and `RunawayTimersError`: Bun throws errors of its own, so
|
|
52
|
+
// a test comparing a failure against uf's class would be comparing it against
|
|
53
|
+
// a class nothing throws.
|
|
54
|
+
|
|
55
|
+
import * as bun from "bun:test";
|
|
56
|
+
|
|
57
|
+
import { equals, render } from "../internal/equality.js";
|
|
58
|
+
import { mocked, waitFor, waitUntil } from "../internal/namespace.js";
|
|
59
|
+
import { DEFAULT_TIMEOUT_MS, NAME_SEPARATOR } from "../internal/run.js";
|
|
60
|
+
import { UnsupportedError } from "../internal/unsupported.js";
|
|
61
|
+
|
|
62
|
+
export { UnsupportedError, equals, render, DEFAULT_TIMEOUT_MS, NAME_SEPARATOR };
|
|
63
|
+
export { afterAll, afterEach, beforeAll, beforeEach } from "bun:test";
|
|
64
|
+
|
|
65
|
+
/** What every refusal here says about where the suite is running. */
|
|
66
|
+
const UNDER_BUN = "the suite is being run by `bun test` (`test.runner` in uf.config.js)";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The error for something that is not a `uft` member.
|
|
70
|
+
*
|
|
71
|
+
* Still an `UnsupportedError`, because that is the class a test catches;
|
|
72
|
+
* only the message changes, since `UnsupportedError`'s is written for `uft`
|
|
73
|
+
* members and `it.skipBecause` or a matcher is not one.
|
|
74
|
+
*/
|
|
75
|
+
class RunnerUnsupportedError extends UnsupportedError {
|
|
76
|
+
constructor(binding, reason) {
|
|
77
|
+
super(binding, reason);
|
|
78
|
+
this.message = `${binding} is not available: ${reason}`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A function that refuses, for a binding with no equivalent under Bun. */
|
|
83
|
+
function refusing(binding, reason, Refusal = RunnerUnsupportedError) {
|
|
84
|
+
return () => {
|
|
85
|
+
throw new Refusal(binding, `${UNDER_BUN}, and ${reason}`);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A class that refuses to be constructed or compared against. */
|
|
90
|
+
function refusingClass(name, reason) {
|
|
91
|
+
const refuse = refusing(name, reason);
|
|
92
|
+
return class {
|
|
93
|
+
constructor() {
|
|
94
|
+
refuse();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static [Symbol.hasInstance]() {
|
|
98
|
+
return refuse();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The placeholders uf's `.each` substitutes a row into. */
|
|
104
|
+
const ROW_TOKEN = /%[sjdi]/g;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A row's case name, written the way uf's own runner writes it.
|
|
108
|
+
*
|
|
109
|
+
* Bun's `.each` differs from uf's twice over: it spreads an array row into the
|
|
110
|
+
* body's arguments where uf hands the body the row whole, and its JUnit report
|
|
111
|
+
* records the name with the placeholder still in it (`formats row %s`, once
|
|
112
|
+
* per row). A suite whose cases are named differently by the two runners is a
|
|
113
|
+
* suite whose results cannot be compared between them, so `.each` here is
|
|
114
|
+
* uf's, registering one case per row through Bun's plain registration.
|
|
115
|
+
* Mirrors `formatRow` in `../internal/registry.js`.
|
|
116
|
+
*/
|
|
117
|
+
function formatRow(name, row) {
|
|
118
|
+
const values = Array.isArray(row) ? row : [row];
|
|
119
|
+
let index = 0;
|
|
120
|
+
return name.replace(ROW_TOKEN, (token) => {
|
|
121
|
+
const value = values[index];
|
|
122
|
+
index += 1;
|
|
123
|
+
return token === "%j" ? (JSON.stringify(value) ?? "undefined") : String(value);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* One of uf's registration functions, forwarding to Bun's.
|
|
129
|
+
*
|
|
130
|
+
* Forwarded call by call rather than re-exported, so that a modifier uf has
|
|
131
|
+
* and Bun does not — `it.skipBecause` — can be attached without reaching into
|
|
132
|
+
* Bun's own function object.
|
|
133
|
+
*/
|
|
134
|
+
function registration(bunApi) {
|
|
135
|
+
const api = (...args) => bunApi(...args);
|
|
136
|
+
api.only = (...args) => bunApi.only(...args);
|
|
137
|
+
api.skip = (...args) => bunApi.skip(...args);
|
|
138
|
+
api.todo = (...args) => bunApi.todo(...args);
|
|
139
|
+
api.each = (table) => (name, body, options) => {
|
|
140
|
+
for (const row of table) {
|
|
141
|
+
bunApi(formatRow(name, row), () => body(row), options);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
return api;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const describe = registration(bun.describe);
|
|
148
|
+
export const it = registration(bun.it);
|
|
149
|
+
it.skipBecause = refusing(
|
|
150
|
+
"it.skipBecause",
|
|
151
|
+
'Bun\'s skip carries no reason into its report; write `it.skip`, or run this file with `runner: "uf"`',
|
|
152
|
+
);
|
|
153
|
+
export const test = it;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* `bench`, which `bun:test` has no counterpart for.
|
|
157
|
+
*
|
|
158
|
+
* Its modifiers refuse too, by name, rather than failing on `undefined`.
|
|
159
|
+
*/
|
|
160
|
+
const refuseBench = refusing(
|
|
161
|
+
"bench",
|
|
162
|
+
'`bun:test` has no benchmarks; run them with `runner: "uf"` and `uf test --bench`',
|
|
163
|
+
);
|
|
164
|
+
export const bench = (..._args) => refuseBench();
|
|
165
|
+
bench.only = refuseBench;
|
|
166
|
+
bench.skip = refuseBench;
|
|
167
|
+
bench.todo = refuseBench;
|
|
168
|
+
|
|
169
|
+
/** uf's matchers that Bun's `expect` has no counterpart for. */
|
|
170
|
+
const MATCHERS_BUN_LACKS = [
|
|
171
|
+
"toBeChecked",
|
|
172
|
+
"toBeDisabled",
|
|
173
|
+
"toBeEnabled",
|
|
174
|
+
"toBeInTheDocument",
|
|
175
|
+
"toBeRequired",
|
|
176
|
+
"toBeVisible",
|
|
177
|
+
"toHaveAttribute",
|
|
178
|
+
"toHaveClass",
|
|
179
|
+
"toHaveFocus",
|
|
180
|
+
"toHaveTextContent",
|
|
181
|
+
"toHaveValue",
|
|
182
|
+
"toHaveNoAxeViolations",
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
bun.expect.extend(
|
|
186
|
+
Object.fromEntries(
|
|
187
|
+
MATCHERS_BUN_LACKS.map((matcher) => [
|
|
188
|
+
matcher,
|
|
189
|
+
refusing(
|
|
190
|
+
`expect(…).${matcher}`,
|
|
191
|
+
"Bun's `expect` has no such matcher; assert on the element's properties directly, or run this file with `runner: \"uf\"`",
|
|
192
|
+
),
|
|
193
|
+
]),
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* uf's snapshot matchers.
|
|
199
|
+
*
|
|
200
|
+
* Bun has matchers by these names, and they are still not the same matchers:
|
|
201
|
+
* both runners write `__snapshots__/<file>.snap` in Jest's format, but each
|
|
202
|
+
* keys an entry by its own spelling of the test's name and serialises the value
|
|
203
|
+
* its own way. Passed through, a suite recorded by uf would either fail against
|
|
204
|
+
* its own snapshots or quietly write a second set beside them — and a snapshot
|
|
205
|
+
* that was rewritten rather than compared is the failure snapshots exist to
|
|
206
|
+
* catch.
|
|
207
|
+
*/
|
|
208
|
+
const SNAPSHOT_MATCHERS = ["toMatchSnapshot", "toMatchInlineSnapshot"];
|
|
209
|
+
|
|
210
|
+
bun.expect.extend(
|
|
211
|
+
Object.fromEntries(
|
|
212
|
+
SNAPSHOT_MATCHERS.map((matcher) => [
|
|
213
|
+
matcher,
|
|
214
|
+
refusing(
|
|
215
|
+
`expect(…).${matcher}`,
|
|
216
|
+
'uf and Bun key and serialise snapshots differently, so this would compare against — or rewrite — a snapshot uf did not write; run snapshot tests with `runner: "uf"`',
|
|
217
|
+
),
|
|
218
|
+
]),
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
export const expect = bun.expect;
|
|
223
|
+
|
|
224
|
+
export const fn = (...args) => bun.jest.fn(...args);
|
|
225
|
+
export const spyOn = (...args) => bun.spyOn(...args);
|
|
226
|
+
|
|
227
|
+
export const AssertionError = refusingClass(
|
|
228
|
+
"AssertionError",
|
|
229
|
+
"Bun throws its own assertion errors, so nothing a failure throws is an instance of uf's",
|
|
230
|
+
);
|
|
231
|
+
export const RunawayTimersError = refusingClass(
|
|
232
|
+
"RunawayTimersError",
|
|
233
|
+
"Bun's fake clock throws its own error for a timer loop that never ends",
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
/** A `uft` member with no equivalent under Bun. */
|
|
237
|
+
const refusingMember = (member, reason) => refusing(member, reason, UnsupportedError);
|
|
238
|
+
|
|
239
|
+
const MODULE_MOCKING =
|
|
240
|
+
"Bun's `mock.module` rewrites modules that have already imported the real one, which is a different promise from `uft.mock`'s (see guide/testing, \"Which hosts\")";
|
|
241
|
+
|
|
242
|
+
const STUBS =
|
|
243
|
+
"uf puts a stub back before the next file, and `bun test` runs every file in one process where nothing would";
|
|
244
|
+
|
|
245
|
+
export const uft = Object.freeze({
|
|
246
|
+
fn,
|
|
247
|
+
spyOn,
|
|
248
|
+
mocked,
|
|
249
|
+
|
|
250
|
+
clearAllMocks: () => bun.jest.clearAllMocks(),
|
|
251
|
+
resetAllMocks: () => bun.jest.resetAllMocks(),
|
|
252
|
+
restoreAllMocks: () => bun.jest.restoreAllMocks(),
|
|
253
|
+
|
|
254
|
+
stubEnv: refusingMember("stubEnv", STUBS),
|
|
255
|
+
unstubAllEnvs: refusingMember("unstubAllEnvs", STUBS),
|
|
256
|
+
stubGlobal: refusingMember("stubGlobal", STUBS),
|
|
257
|
+
unstubAllGlobals: refusingMember("unstubAllGlobals", STUBS),
|
|
258
|
+
|
|
259
|
+
waitFor,
|
|
260
|
+
waitUntil,
|
|
261
|
+
|
|
262
|
+
useFakeTimers: () => {
|
|
263
|
+
bun.jest.useFakeTimers();
|
|
264
|
+
},
|
|
265
|
+
useRealTimers: () => {
|
|
266
|
+
bun.jest.useRealTimers();
|
|
267
|
+
},
|
|
268
|
+
isFakeTimers: () => bun.jest.isFakeTimers(),
|
|
269
|
+
advanceTimersByTime: (milliseconds) => {
|
|
270
|
+
bun.jest.advanceTimersByTime(milliseconds);
|
|
271
|
+
},
|
|
272
|
+
advanceTimersByTimeAsync: refusingMember(
|
|
273
|
+
"advanceTimersByTimeAsync",
|
|
274
|
+
"Bun's fake clock has no asynchronous advance; await between `uft.advanceTimersByTime` calls instead",
|
|
275
|
+
),
|
|
276
|
+
advanceTimersToNextTimer: (steps) => {
|
|
277
|
+
bun.jest.advanceTimersToNextTimer(steps);
|
|
278
|
+
},
|
|
279
|
+
runAllTimers: () => {
|
|
280
|
+
bun.jest.runAllTimers();
|
|
281
|
+
},
|
|
282
|
+
runOnlyPendingTimers: () => {
|
|
283
|
+
bun.jest.runOnlyPendingTimers();
|
|
284
|
+
},
|
|
285
|
+
getTimerCount: () => bun.jest.getTimerCount(),
|
|
286
|
+
// uf's clock only moves while it is faked, and a time set before
|
|
287
|
+
// `useFakeTimers` is forgotten when the clock is installed. Bun's
|
|
288
|
+
// `setSystemTime` moves the real `Date` whether or not anything is faked, so
|
|
289
|
+
// it is only forwarded while Bun's clock is faked — outside that it does
|
|
290
|
+
// what uf's does, which is nothing a test can observe.
|
|
291
|
+
setSystemTime: (time) => {
|
|
292
|
+
if (bun.jest.isFakeTimers()) {
|
|
293
|
+
bun.setSystemTime(time);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
getMockedSystemTime: () => (bun.jest.isFakeTimers() ? new Date() : null),
|
|
297
|
+
|
|
298
|
+
mock: refusingMember("mock", MODULE_MOCKING),
|
|
299
|
+
doMock: refusingMember("doMock", MODULE_MOCKING),
|
|
300
|
+
unmock: refusingMember("unmock", MODULE_MOCKING),
|
|
301
|
+
doUnmock: refusingMember("doUnmock", MODULE_MOCKING),
|
|
302
|
+
importActual: refusingMember("importActual", MODULE_MOCKING),
|
|
303
|
+
importMock: refusingMember("importMock", MODULE_MOCKING),
|
|
304
|
+
resetModules: refusingMember("resetModules", MODULE_MOCKING),
|
|
305
|
+
});
|
package/in-source.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
//
|
|
3
|
+
// In-source tests: `if (import.meta.uf.test) { … }` in the file being tested.
|
|
4
|
+
//
|
|
5
|
+
// A three-line test for a three-line pure function does not want a file of its
|
|
6
|
+
// own, an import of the thing it tests, and a path that has to be kept in step
|
|
7
|
+
// with it. `import.meta.uf.test` is the block that holds it, and it is the
|
|
8
|
+
// same idea as Vitest's `import.meta.vitest` for the same reason.
|
|
9
|
+
//
|
|
10
|
+
// # What a block looks like
|
|
11
|
+
//
|
|
12
|
+
// import { expect, it } from "@uniflowed/test";
|
|
13
|
+
//
|
|
14
|
+
// export function add(a: number, b: number): number {
|
|
15
|
+
// return a + b;
|
|
16
|
+
// }
|
|
17
|
+
//
|
|
18
|
+
// if (import.meta.uf.test) {
|
|
19
|
+
// it("adds", () => {
|
|
20
|
+
// expect(add(1, 2)).toBe(3);
|
|
21
|
+
// });
|
|
22
|
+
// }
|
|
23
|
+
//
|
|
24
|
+
// An ordinary import for the bindings, because that is what makes them typed:
|
|
25
|
+
// Flow has no `typeof import("…")` for a library definition to use, so a
|
|
26
|
+
// marker that carried the API could not be described to the checker. The
|
|
27
|
+
// marker's *value* is the API anyway — `const { it } = import.meta.uf.test` is
|
|
28
|
+
// what somebody arriving from Vitest writes and it works — but it is untyped,
|
|
29
|
+
// and the import is the form uf recommends.
|
|
30
|
+
//
|
|
31
|
+
// The import costs nothing in a build. The branch folds to `if (void 0)` and
|
|
32
|
+
// goes, `@uniflowed/test` declares `sideEffects: false` so the now-unused
|
|
33
|
+
// import goes with it, and the package carries a `browser` field mapping the
|
|
34
|
+
// six `node:` builtins its edges import at `./internal/browser/node.js` — so
|
|
35
|
+
// the bundler does not even print a warning about modules it is in the middle
|
|
36
|
+
// of removing.
|
|
37
|
+
//
|
|
38
|
+
// # What makes the block disappear
|
|
39
|
+
//
|
|
40
|
+
// `uf` compiles the marker away rather than leaving it to be falsy at runtime:
|
|
41
|
+
// `crates/uf_transform`'s printer substitutes `void 0` for
|
|
42
|
+
// `import.meta.uf.test` in every transform except the ones `uf test` asks for,
|
|
43
|
+
// so `uf build` sees `if (void 0)` and the bundler removes the block. There is
|
|
44
|
+
// no `import.meta.uf` at runtime, in any host, which is also why the block
|
|
45
|
+
// cannot throw in a browser that never heard of uf.
|
|
46
|
+
//
|
|
47
|
+
// `tests/library/in-source.test.js` and `tests/library/in-source-subject.js`
|
|
48
|
+
// are the block running. The other half is in `crates/uf_cli/tests/vite.rs`,
|
|
49
|
+
// which builds a fixture whose block holds a string that appears nowhere else
|
|
50
|
+
// and reads the emitted client bundle back looking for it — in the same file
|
|
51
|
+
// the module's other marker has to be present in, because "a string is
|
|
52
|
+
// missing" is only evidence when something establishes the module is not.
|
|
53
|
+
//
|
|
54
|
+
// # Why the marker is a call, and what it is called with
|
|
55
|
+
//
|
|
56
|
+
// Under `uf test` the printer substitutes
|
|
57
|
+
// `globalThis.__ufInSourceTests?.(import.meta.url)`, and this module is what
|
|
58
|
+
// answers it. The argument is the module asking, because a source file with an
|
|
59
|
+
// in-source block is an ordinary module and is imported more than once in a
|
|
60
|
+
// normal run: `uf test` imports `add.js` as the file it is running, and
|
|
61
|
+
// `add.test.js` imports it again for the function. Both evaluations reach the
|
|
62
|
+
// marker. Only the first is the file the run is reporting on, so only the
|
|
63
|
+
// first is given the API — otherwise every in-source case would register twice
|
|
64
|
+
// and the second copy would be filed under whichever test file imported it.
|
|
65
|
+
|
|
66
|
+
import {
|
|
67
|
+
afterAll,
|
|
68
|
+
afterEach,
|
|
69
|
+
beforeAll,
|
|
70
|
+
beforeEach,
|
|
71
|
+
describe,
|
|
72
|
+
it,
|
|
73
|
+
test,
|
|
74
|
+
} from "./internal/registry.js";
|
|
75
|
+
import { fn, spyOn } from "./internal/spy.js";
|
|
76
|
+
|
|
77
|
+
import type { Expect } from "./internal/expect.js";
|
|
78
|
+
import type { Uft } from "./internal/namespace.js";
|
|
79
|
+
import { expect } from "./internal/expect.js";
|
|
80
|
+
import { uft } from "./internal/namespace.js";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The name `uf` compiles `import.meta.uf.test` into a call on.
|
|
84
|
+
*
|
|
85
|
+
* Exported so the worker and the tests name it once; it is spelled out in `crates/uf_transform/src/print.rs` as well, and
|
|
86
|
+
* `tests/library/in-source.test.js` is what keeps the two spellings equal.
|
|
87
|
+
*/
|
|
88
|
+
export const IN_SOURCE_GLOBAL: "__ufInSourceTests" = "__ufInSourceTests";
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* What an in-source block is handed.
|
|
92
|
+
*
|
|
93
|
+
* The subset of `@uniflowed/test` a test body needs, and deliberately not all
|
|
94
|
+
* of it: a block that wants module mocking or a snapshot is a block that has
|
|
95
|
+
* outgrown living inside the file it tests, and `import { uft } from
|
|
96
|
+
* "@uniflowed/test"` in a file of its own is the answer to that. `uft` is here
|
|
97
|
+
* anyway because spies and the fake clock are ordinary for a unit test.
|
|
98
|
+
*/
|
|
99
|
+
export type InSourceTests = {
|
|
100
|
+
readonly describe: typeof describe,
|
|
101
|
+
readonly it: typeof it,
|
|
102
|
+
readonly test: typeof test,
|
|
103
|
+
readonly expect: Expect,
|
|
104
|
+
readonly beforeAll: typeof beforeAll,
|
|
105
|
+
readonly beforeEach: typeof beforeEach,
|
|
106
|
+
readonly afterAll: typeof afterAll,
|
|
107
|
+
readonly afterEach: typeof afterEach,
|
|
108
|
+
readonly fn: typeof fn,
|
|
109
|
+
readonly spyOn: typeof spyOn,
|
|
110
|
+
readonly uft: Uft,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The API every in-source block in a run shares.
|
|
115
|
+
*
|
|
116
|
+
* One frozen object rather than one per file: the bindings are the module's
|
|
117
|
+
* own singletons — `it` registers into the one registry the worker resets
|
|
118
|
+
* between files — so a copy per file would only be a copy of the same
|
|
119
|
+
* references.
|
|
120
|
+
*/
|
|
121
|
+
const API: InSourceTests = Object.freeze({
|
|
122
|
+
describe,
|
|
123
|
+
it,
|
|
124
|
+
test,
|
|
125
|
+
expect,
|
|
126
|
+
beforeAll,
|
|
127
|
+
beforeEach,
|
|
128
|
+
afterAll,
|
|
129
|
+
afterEach,
|
|
130
|
+
fn,
|
|
131
|
+
spyOn,
|
|
132
|
+
uft,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The module URL, without whatever a host appended to it.
|
|
137
|
+
*
|
|
138
|
+
* The worker busts its import cache with `?uf-run=<generation>` so a watch
|
|
139
|
+
* rerun sees the edited file, which means the file being run reports an
|
|
140
|
+
* `import.meta.url` that its own path does not match. Comparing the part
|
|
141
|
+
* before the query is comparing the module.
|
|
142
|
+
*
|
|
143
|
+
* A hash is stripped for the same reason and never appears in practice; it
|
|
144
|
+
* costs one `indexOf` to not have to think about it again.
|
|
145
|
+
*/
|
|
146
|
+
function moduleUrl(url: string): string {
|
|
147
|
+
const end = url.search(/[?#]/);
|
|
148
|
+
return end === -1 ? url : url.slice(0, end);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Hand in-source blocks their API for the file `url`, and nothing to any other
|
|
153
|
+
* module.
|
|
154
|
+
*
|
|
155
|
+
* Returns the function that puts the global back as it was, which the worker
|
|
156
|
+
* calls when the file is over. Restoring rather than leaving it set is the
|
|
157
|
+
* same rule the worker applies to environment stubs and module mocks: a file
|
|
158
|
+
* may not change what the next file on this worker sees.
|
|
159
|
+
*/
|
|
160
|
+
export function installInSourceTests(url: string): () => void {
|
|
161
|
+
const wanted = moduleUrl(url);
|
|
162
|
+
const globals: { [string]: mixed } = globalThis as $FlowFixMe;
|
|
163
|
+
const previous = globals[IN_SOURCE_GLOBAL];
|
|
164
|
+
globals[IN_SOURCE_GLOBAL] = (asking: string): InSourceTests | void =>
|
|
165
|
+
moduleUrl(asking) === wanted ? API : undefined;
|
|
166
|
+
return () => {
|
|
167
|
+
globals[IN_SOURCE_GLOBAL] = previous;
|
|
168
|
+
};
|
|
169
|
+
}
|
package/index.js
CHANGED
|
@@ -12,7 +12,16 @@
|
|
|
12
12
|
//
|
|
13
13
|
// The whole surface is importable from here, so a test file has one import.
|
|
14
14
|
|
|
15
|
-
export type {
|
|
15
|
+
export type { Expect, Expectation, Matchers } from "./internal/expect.js";
|
|
16
|
+
export type { InSourceTests } from "./in-source.js";
|
|
17
|
+
export type {
|
|
18
|
+
BenchOptions,
|
|
19
|
+
Body as TestBody,
|
|
20
|
+
Case,
|
|
21
|
+
Modifier,
|
|
22
|
+
Suite,
|
|
23
|
+
TestOptions,
|
|
24
|
+
} from "./internal/registry.js";
|
|
16
25
|
export type { ModuleFactory, ModuleNamespace } from "./internal/modules.js";
|
|
17
26
|
export type { Uft } from "./internal/namespace.js";
|
|
18
27
|
export type { Outcome, Result, RunOptions } from "./internal/run.js";
|
|
@@ -25,6 +34,7 @@ export {
|
|
|
25
34
|
afterEach,
|
|
26
35
|
beforeAll,
|
|
27
36
|
beforeEach,
|
|
37
|
+
bench,
|
|
28
38
|
describe,
|
|
29
39
|
it,
|
|
30
40
|
test,
|