@starci/jest-preset 2.3.6 → 2.4.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +1 -1
  3. package/README.md +6 -6
  4. package/{builders.cjs → dist/builders.d.ts} +2 -7
  5. package/dist/builders.js +15 -0
  6. package/dist/cache.d.ts +48 -0
  7. package/dist/cache.js +68 -0
  8. package/dist/clock.d.ts +20 -0
  9. package/dist/clock.js +32 -0
  10. package/dist/entity-manager.d.ts +114 -0
  11. package/dist/entity-manager.js +138 -0
  12. package/dist/event-bus.d.ts +52 -0
  13. package/dist/event-bus.js +106 -0
  14. package/dist/ids.d.ts +21 -0
  15. package/dist/ids.js +42 -0
  16. package/dist/inbox.d.ts +65 -0
  17. package/dist/inbox.js +173 -0
  18. package/dist/index.d.ts +114 -0
  19. package/dist/index.js +289 -0
  20. package/dist/lcov.d.ts +16 -0
  21. package/dist/lcov.js +25 -0
  22. package/dist/lock.d.ts +37 -0
  23. package/dist/lock.js +57 -0
  24. package/dist/matchers.d.ts +27 -0
  25. package/dist/matchers.js +36 -0
  26. package/dist/merge.d.ts +14 -0
  27. package/dist/merge.js +55 -0
  28. package/dist/mock.d.ts +21 -0
  29. package/dist/mock.js +52 -0
  30. package/dist/queue.d.ts +30 -0
  31. package/dist/queue.js +50 -0
  32. package/dist/sonar-exclusions.d.ts +7 -0
  33. package/dist/sonar-exclusions.js +8 -0
  34. package/dist/unit-metadata.d.ts +23 -0
  35. package/dist/unit-metadata.js +71 -0
  36. package/dist/world-runner.d.ts +42 -0
  37. package/dist/world-runner.js +142 -0
  38. package/package.json +52 -32
  39. package/builders.d.ts +0 -2
  40. package/cache.cjs +0 -67
  41. package/cache.d.ts +0 -31
  42. package/clock.cjs +0 -34
  43. package/clock.d.ts +0 -13
  44. package/entity-manager.cjs +0 -146
  45. package/entity-manager.d.ts +0 -103
  46. package/event-bus.cjs +0 -100
  47. package/event-bus.d.ts +0 -54
  48. package/ids.cjs +0 -41
  49. package/ids.d.ts +0 -13
  50. package/inbox.cjs +0 -162
  51. package/inbox.d.ts +0 -58
  52. package/index.cjs +0 -295
  53. package/index.d.ts +0 -38
  54. package/lcov.cjs +0 -25
  55. package/lock.cjs +0 -56
  56. package/lock.d.ts +0 -26
  57. package/matchers.cjs +0 -45
  58. package/matchers.d.ts +0 -17
  59. package/merge.cjs +0 -45
  60. package/mock.cjs +0 -53
  61. package/mock.d.ts +0 -12
  62. package/queue.cjs +0 -51
  63. package/queue.d.ts +0 -26
  64. package/sonar-exclusions.cjs +0 -8
  65. package/unit-metadata.cjs +0 -63
  66. package/world-runner.cjs +0 -142
  67. /package/{unit-metadata.generated.cjs → dist/unit-metadata.generated.js} +0 -0
package/mock.cjs DELETED
@@ -1,53 +0,0 @@
1
- "use strict"
2
-
3
- /**
4
- * `mock<T>()` -- a typed test double that replaces the `{ ... } as unknown as T` cast.
5
- *
6
- * Every property read yields one stable `jest.fn()` (created on first read), so `mock<Repo>().find` is
7
- * already a mock function, `expect(m.find).toHaveBeenCalledWith(...)` works, and `m.find.mockResolvedValue(x)`
8
- * type-checks against `Repo["find"]`. Values passed in `overrides` win over the generated functions and may be
9
- * partial: the return type is still `T`.
10
- *
11
- * `createMock` takes the function factory as an argument (called with the property name) so the helper has no hard
12
- * dependency on a global `jest`; `mock` binds it to `jest.fn`.
13
- */
14
-
15
- // Probed by promise resolution, jest matchers and serializers: they must read as absent, not as a mock function.
16
- const ABSENT = new Set(["then", "asymmetricMatch", "$$typeof", "nodeType", "tagName", "toJSON"])
17
-
18
- function createMock(makeFn) {
19
- return function mock(overrides) {
20
- const own = new Map()
21
- const store = overrides ? { ...overrides } : {}
22
- return new Proxy(store, {
23
- get(target, property, receiver) {
24
- if (Object.hasOwn(target, property)) return Reflect.get(target, property, receiver)
25
- if (typeof property === "symbol" || property in Object.prototype) return Reflect.get(target, property, receiver)
26
- if (ABSENT.has(property)) return undefined
27
- if (!own.has(property)) own.set(property, makeFn(property))
28
- return own.get(property)
29
- },
30
- set(target, property, value) {
31
- own.delete(property)
32
- target[property] = value
33
- return true
34
- },
35
- has(target, property) {
36
- return Object.hasOwn(target, property) || own.has(property)
37
- },
38
- })
39
- }
40
- }
41
-
42
- // jest-runtime hands every module it loads, this one included, a module-scoped `jest` object; it is not a property
43
- // of globalThis, so it is read as a free identifier. @jest/globals is the fallback for a runner that does not.
44
- function jestFn(implementation) {
45
- if (typeof jest !== "undefined" && typeof jest.fn === "function") return jest.fn(implementation)
46
- try {
47
- return require("@jest/globals").jest.fn(implementation)
48
- } catch {
49
- throw new Error("@starci/jest-preset mock<T>() needs the jest runtime; call it from a jest test")
50
- }
51
- }
52
-
53
- module.exports = { mock: createMock(() => jestFn()), createMock, jestFn }
package/mock.d.ts DELETED
@@ -1,12 +0,0 @@
1
- /// <reference types="jest" />
2
-
3
- /** Function members become jest mocks typed against the real signature; everything else stays as declared. */
4
- export type MockOf<T> = T & {
5
- [K in keyof T]: T[K] extends (...args: infer A) => infer R ? jest.Mock<R, A> & T[K] : T[K]
6
- }
7
-
8
- /** A typed test double for `T`. `overrides` may be partial; the result is usable wherever a `T` is required. */
9
- export declare function mock<T extends object>(overrides?: Partial<T>): MockOf<T>
10
-
11
- /** The factory behind `mock`, for callers that bring their own function factory (used by the package spec). */
12
- export declare function createMock(makeFn: () => unknown): <T extends object>(overrides?: Partial<T>) => MockOf<T>
package/queue.cjs DELETED
@@ -1,51 +0,0 @@
1
- "use strict"
2
-
3
- /**
4
- * `recordingQueueOutbox()` -- a `QueueOutbox` double that records what a typed queue producer writes, so a spec asserts on `outbox.jobs`.
5
- *
6
- * `write(tx, queue, payload)` records the job with the manager it was written through. `jobs` is every job in order, `jobsOf(queue)`
7
- * the jobs of one queue, and `entries` adds `inTransaction`: whether `tx` was the view a `fakeTransaction` body received, so a spec proves
8
- * a job is enqueued in the transaction of the change that needs it. `failNext('write', error)` makes the next write reject once.
9
- */
10
- const { isTransactionManager } = require("./entity-manager.cjs")
11
-
12
- function recordingQueueOutbox() {
13
- const written = []
14
- const failures = new Map()
15
- return {
16
- async write(tx, queue, payload) {
17
- if (failures.has("write")) {
18
- const error = failures.get("write")
19
- failures.delete("write")
20
- throw error
21
- }
22
- written.push({ queue, payload, tx, inTransaction: isTransactionManager(tx) })
23
- },
24
- /** Every job written, in order: `{ queue, payload }`. */
25
- get jobs() {
26
- return written.map(({ queue, payload }) => ({ queue, payload }))
27
- },
28
- /** Every write with its manager and `inTransaction`. */
29
- get entries() {
30
- return [...written]
31
- },
32
- /** The jobs of one queue. */
33
- jobsOf(queue) {
34
- return this.jobs.filter((job) => job.queue === queue)
35
- },
36
- /** True when something was written and every write went through a transaction manager. */
37
- get allInTransaction() {
38
- return written.length > 0 && written.every((entry) => entry.inTransaction)
39
- },
40
- /** Makes the next write reject with `error`, once. */
41
- failNext(operation, error) {
42
- failures.set(operation, error)
43
- },
44
- clear() {
45
- written.length = 0
46
- failures.clear()
47
- },
48
- }
49
- }
50
-
51
- module.exports = { recordingQueueOutbox }
package/queue.d.ts DELETED
@@ -1,26 +0,0 @@
1
- /** One recorded job write. */
2
- export interface QueueWrite {
3
- readonly queue: string
4
- readonly payload: object
5
- readonly tx: unknown
6
- /** Whether `tx` was the view a `fakeTransaction` body received. */
7
- readonly inTransaction: boolean
8
- }
9
-
10
- /** A `QueueOutbox` double that records the jobs a typed producer writes. */
11
- export interface RecordingQueueOutbox {
12
- write(tx: unknown, queue: string, payload: object): Promise<void>
13
- /** Every job written, in order. */
14
- readonly jobs: ReadonlyArray<{ readonly queue: string; readonly payload: object }>
15
- /** Every write with its manager. */
16
- readonly entries: ReadonlyArray<QueueWrite>
17
- jobsOf(queue: string): ReadonlyArray<{ readonly queue: string; readonly payload: object }>
18
- /** True when something was written and every write went through a `fakeTransaction` manager. */
19
- readonly allInTransaction: boolean
20
- /** Makes the next write reject with `error`, once. */
21
- failNext(operation: "write", error: Error): void
22
- clear(): void
23
- }
24
-
25
- /** The queue outbox double. */
26
- export declare function recordingQueueOutbox(): RecordingQueueOutbox
@@ -1,8 +0,0 @@
1
- "use strict"
2
-
3
- /**
4
- * Sonar `sonar.exclusions`: specs, e2e specs, dist and coverage output are not analysed at all. A plain list with no require, so
5
- * @starci/hfs ships this file byte for byte (packages/hfs/runtime, scripts/hfs/sync-runtime.mjs) and `starci app scaffold` renders
6
- * the same exclusions before any app dependency is installed.
7
- */
8
- module.exports = Object.freeze(["**/*.spec.ts", "**/*.e2e-spec.ts", "**/dist/**", "**/coverage/**"])
package/unit-metadata.cjs DELETED
@@ -1,63 +0,0 @@
1
- "use strict"
2
-
3
- const METADATA_SOURCE = require.resolve("./unit-metadata.generated.cjs")
4
- const METADATA_TEXT = require("node:fs").readFileSync(METADATA_SOURCE, "utf8")
5
-
6
- /** Only synthetic conditional guards inside compiler-emitted parameter metadata map to the generated-metadata anchor: a zero-width position at its end, which no line covers, so they are measured nowhere and source metadata calls stay measured. */
7
- function factory(compiler) {
8
- const ts = compiler.configSet.compilerModule
9
- const source = ts.createSourceMapSource(METADATA_SOURCE, METADATA_TEXT)
10
- return (context) => {
11
- const visit = (node) => {
12
- if (isGeneratedMetadata(ts, node)) {
13
- const guard = (child) => {
14
- if (isGeneratedGuard(ts, child)) {
15
- ts.setSourceMapRange(child, { pos: METADATA_TEXT.length, end: METADATA_TEXT.length, source })
16
- ts.setEmitFlags(child, ts.getEmitFlags(child) | ts.EmitFlags.NoNestedSourceMaps)
17
- return child
18
- }
19
- return ts.visitEachChild(child, guard, context)
20
- }
21
- return ts.visitEachChild(node, guard, context)
22
- }
23
- return ts.visitEachChild(node, visit, context)
24
- }
25
- return (file) => ts.visitNode(file, visit)
26
- }
27
- }
28
-
29
- /** Only TypeScript's synthetic __metadata("design:paramtypes", ...) call is eligible; source calls remain measured. */
30
- function isGeneratedMetadata(ts, node) {
31
- return node.pos < 0 && ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "__metadata" && node.arguments.length === 2 && ts.isStringLiteral(node.arguments[0]) && node.arguments[0].text === "design:paramtypes"
32
- }
33
-
34
- /** A metadata exclusion requires TypeScript's synthetic `typeof value === "function" ? value : Object` guard shape. */
35
- function isGeneratedGuard(ts, node) {
36
- if (node.pos >= 0 || !ts.isConditionalExpression(node)) return false
37
- const condition = node.condition
38
- return ts.isBinaryExpression(condition) && condition.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken && ts.isTypeOfExpression(condition.left) && ts.isStringLiteral(condition.right) && condition.right.text === "function" && ts.isIdentifier(node.whenFalse) && node.whenFalse.text === "Object"
39
- }
40
-
41
- /** ts-jest normalizes map sources to the input file; restore the generated metadata anchor before Jest reads the map. */
42
- function restoreMetadataSource(result) {
43
- const prefix = "sourceMappingURL=data:application/json;charset=utf-8;base64,"
44
- const offset = result.code.lastIndexOf(prefix)
45
- if (offset < 0) return result
46
- const start = offset + prefix.length
47
- const map = JSON.parse(Buffer.from(result.code.slice(start), "base64").toString("utf8"))
48
- if (map.sourcesContent?.length !== 2 || map.sourcesContent[1] !== METADATA_TEXT) return result
49
- map.sources = [map.sources[0], METADATA_SOURCE]
50
- return { ...result, code: result.code.slice(0, start) + Buffer.from(JSON.stringify(map)).toString("base64") }
51
- }
52
-
53
- /** Unit transforms delegate compilation and caching to ts-jest; the after-transformer changes metadata mappings only. */
54
- function createTransformer(options) {
55
- const delegate = require("ts-jest").default.createTransformer(options)
56
- return {
57
- getCacheKey: (...args) => delegate.getCacheKey(...args) + ":starci-unit-metadata-6",
58
- process: (...args) => restoreMetadataSource(delegate.process(...args)),
59
- processAsync: async (...args) => restoreMetadataSource(await delegate.processAsync(...args)),
60
- }
61
- }
62
-
63
- module.exports = { name: "starci-unit-metadata", version: 6, factory, createTransformer }
package/world-runner.cjs DELETED
@@ -1,142 +0,0 @@
1
- "use strict"
2
-
3
- /**
4
- * The runner of the integration, e2e and contract projects: every spec file runs in a process of its own, bound to one data
5
- * slot of the test world.
6
- *
7
- * Why a process per file: jest runs a file in a fresh vm context, but a context still reaches the globals of the process that
8
- * hosts it, and jest-environment-node exposes every global the host process had when the environment loaded. The test world's
9
- * globalSetup runs in jest's main process and loads application code there (the migrate step and its modules), so a framework
10
- * that keeps its registry on a process global (`@nestjs/graphql` keeps `GqlTypeMetadataStorage` on `global`) leaks it into
11
- * every file jest runs in that process; and a worker that runs several files carries whatever the first one left on its own
12
- * global. A process per file isolates every framework at once.
13
- *
14
- * Why slots: the test world resets a slot's data (its databases, realm users, Redis DB, buckets and fakes) when a file boots,
15
- * so two files must never share a slot at the same time. The globalSetup of `@starci/test-world` provisions N slots, each a
16
- * complete, independent set of data namespaces, and publishes them in its state file (`STARCI_TEST_WORLD_STATE`, protocol
17
- * {@link WORLD_STATE_VERSION}). The runner runs up to `min(--maxWorkers, N)` files at once and gives each a free slot: the
18
- * file's process is forked with `STARCI_TEST_WORLD_SLOT=<k>`; the slot goes back to the pool when the file ends.
19
- *
20
- * How: the stock jest-runner, driven one file at a time per slot with a single-worker farm (`maxWorkers: 1` per file), which
21
- * forks a fresh child, runs the file there and ends the child. jest-worker forks in the farm's constructor, which jest-runner
22
- * calls before its first `await`, so the slot variable is set on `process.env` only for that synchronous span. Never in band,
23
- * even when jest would choose it (`--runInBand`): the main process holds the globalSetup's state.
24
- *
25
- * The pair is exact: this runner reads protocol {@link WORLD_STATE_VERSION} only. Any other state file is the wrong
26
- * `@starci/test-world` beside this preset, a typed failure naming both versions (re-pin both per canon-pins), never a fallback.
27
- */
28
-
29
- const { createRequire } = require("node:module")
30
- const fs = require("node:fs")
31
- const path = require("node:path")
32
-
33
- /** The state-file protocol of `@starci/test-world` this runner speaks (test-world 1.1.x). */
34
- const WORLD_STATE_VERSION = 2
35
- /** The variable that carries the path of the world's state file (set by its globalSetup in this process). */
36
- const STATE_FILE_ENV = "STARCI_TEST_WORLD_STATE"
37
- /** The variable that tells a file's process which slot it owns (1-based). */
38
- const SLOT_ENV = "STARCI_TEST_WORLD_SLOT"
39
- /** The failure code of a state file this runner cannot drive. */
40
- const WORLD_PAIR_MISMATCH = "JEST_PRESET_WORLD_PAIR_MISMATCH"
41
-
42
- const PRESET = (() => {
43
- const own = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
44
- return `${own.name}@${own.version}`
45
- })()
46
-
47
- /** A typed failure of the runner: `code` names it, the message says what to do. */
48
- function runnerError(code, detail) {
49
- const error = new Error(`${code}: ${detail}`)
50
- error.code = code
51
- return error
52
- }
53
-
54
- /** The slot count the world published; a missing or foreign state file is a pair mismatch. */
55
- function worldSlots(env = process.env) {
56
- const file = env[STATE_FILE_ENV]
57
- const fix = `pin @starci/test-world and @starci/jest-preset together as knowledge/hfs/canon-pins.yaml pairs them (${PRESET} speaks state protocol ${WORLD_STATE_VERSION}, @starci/test-world 1.1.x)`
58
- if (file === undefined || file === "") {
59
- throw runnerError(WORLD_PAIR_MISMATCH, `${STATE_FILE_ENV} is not set: the world's globalSetup published no state file for ${PRESET}; ${fix}`)
60
- }
61
- let state
62
- try {
63
- state = JSON.parse(fs.readFileSync(file, "utf8"))
64
- } catch (cause) {
65
- throw runnerError(WORLD_PAIR_MISMATCH, `the world state file ${file} cannot be read (${cause instanceof Error ? cause.message : String(cause)}); ${fix}`)
66
- }
67
- const library = typeof state?.library === "string" ? state.library : "@starci/test-world before 1.1.0"
68
- if (state?.version !== WORLD_STATE_VERSION || !Array.isArray(state.slots) || state.slots.length < 1) {
69
- throw runnerError(WORLD_PAIR_MISMATCH, `${library} wrote state protocol ${String(state?.version)}, ${PRESET} needs ${WORLD_STATE_VERSION}; ${fix}`)
70
- }
71
- return state.slots.length
72
- }
73
-
74
- /**
75
- * The stock jest-runner of the jest that loads this preset: resolved through jest's own dependency chain
76
- * (jest -> @jest/core -> jest-runner) from the repository root, so the runner always matches the running jest.
77
- */
78
- function stockRunner(root = process.cwd()) {
79
- const fromRoot = createRequire(path.join(root, "package.json"))
80
- const fromJest = createRequire(fromRoot.resolve("jest/package.json"))
81
- const fromCore = createRequire(fromJest.resolve("@jest/core"))
82
- const loaded = fromCore("jest-runner")
83
- return loaded.default ?? loaded
84
- }
85
-
86
- const StockRunner = stockRunner()
87
-
88
- class WorldRunner extends StockRunner {
89
- #listeners = []
90
-
91
- /** Jest subscribes before the run; the subscriptions are handed to the per-file runner of each file. */
92
- on(eventName, listener) {
93
- const entry = { eventName, listener }
94
- this.#listeners.push(entry)
95
- return () => {
96
- this.#listeners = this.#listeners.filter((candidate) => candidate !== entry)
97
- }
98
- }
99
-
100
- /** Runs the files up to `min(--maxWorkers, slots)` at once, each in a fresh process bound to a free slot; never in band. */
101
- async runTests(tests, watcher) {
102
- const slots = worldSlots()
103
- const width = Math.max(1, Math.min(this._globalConfig.maxWorkers ?? 1, slots))
104
- const queue = [...tests]
105
- // one lane per slot: a lane owns its slot for the whole run and takes the next file when its current one ends
106
- const lane = async (slot) => {
107
- for (let test = queue.shift(); test !== undefined; test = queue.shift()) {
108
- if (watcher.isInterrupted()) return
109
- await this.#runOne(test, watcher, slot)
110
- }
111
- }
112
- await Promise.all(Array.from({ length: Math.min(width, tests.length) }, (_, index) => lane(index + 1)))
113
- }
114
-
115
- /** One file in a fresh single-worker farm whose child is forked with the slot in its environment. */
116
- async #runOne(test, watcher, slot) {
117
- const runner = new StockRunner({ ...this._globalConfig, maxWorkers: 1 }, this._context)
118
- const unsubscribe = this.#listeners.map(({ eventName, listener }) => runner.on(eventName, listener))
119
- const before = process.env[SLOT_ENV]
120
- let running
121
- process.env[SLOT_ENV] = String(slot)
122
- try {
123
- // the farm (and its child) is created synchronously inside this call, before jest-runner's first await
124
- running = runner.runTests([test], watcher, { serial: false })
125
- } finally {
126
- if (before === undefined) delete process.env[SLOT_ENV]
127
- else process.env[SLOT_ENV] = before
128
- }
129
- try {
130
- await running
131
- } finally {
132
- for (const stop of unsubscribe) stop()
133
- }
134
- }
135
- }
136
-
137
- WorldRunner.WORLD_STATE_VERSION = WORLD_STATE_VERSION
138
- WorldRunner.SLOT_ENV = SLOT_ENV
139
- WorldRunner.WORLD_PAIR_MISMATCH = WORLD_PAIR_MISMATCH
140
- WorldRunner.worldSlots = worldSlots
141
-
142
- module.exports = WorldRunner