@starci/jest-preset 2.3.5 → 2.3.7
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/LICENSE +1 -1
- package/README.md +17 -4
- package/entity-manager.cjs +1 -0
- package/entity-manager.d.ts +1 -1
- package/inbox.cjs +96 -5
- package/inbox.d.ts +33 -4
- package/index.cjs +9 -1
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.7 - 2026-10-27
|
|
4
|
+
|
|
5
|
+
- Changed: licensed MIT (was UNLICENSED); the package metadata names the repository, homepage and bugs so npm can attach provenance. No rule or behaviour changed.
|
|
6
|
+
|
|
7
|
+
## 2.3.6 - 2026-10-25
|
|
8
|
+
- Added: the fake inbox records client operations and sweeps (`clientEntries`, `sweeps`) and the entity-manager and index doubles follow the idempotent client inbox of the alpha.31 cohort; the lite and full example back ends spec against them.
|
|
9
|
+
|
|
3
10
|
## 2.3.5 - 2026-10-23
|
|
4
11
|
- Changed: the preset sets no `testTimeout` (the jest default applies; a world spec sets its own), owner ruling 2026-10-22: no loosening.
|
|
5
12
|
- Changed: the type probes assert their compiler diagnostics (file, line, code) from their own rejected fixtures instead of inline ts directives; the generated metadata anchor carries no coverage-exclusion comment; a spec refuses any inline suppression in the package.
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @starci/jest-preset
|
|
2
2
|
|
|
3
|
-
The jest config of a Nest repository and the one shared unit-test double kit (`mock<T>()`, `mockEntityManager`, `fakeTransaction`, `fakeCache`, `fakeLock`, `recordingEventBus`, `recordingQueueOutbox`, `fakeInbox`, `builder`, `FakeClock`, `fakeIds` and the Outcome matchers). Install it from the npm registry at the exact version in
|
|
3
|
+
The jest config of a Nest repository and the one shared unit-test double kit (`mock<T>()`, `mockEntityManager`, `fakeTransaction`, `fakeCache`, `fakeLock`, `recordingEventBus`, `recordingQueueOutbox`, `fakeInbox`, `builder`, `FakeClock`, `fakeIds` and the Outcome matchers). Install it from the npm registry at the exact version in the StarCi runtime's `knowledge/stack/pins.yaml` (see [`packages/README.md`](../README.md)).
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
// jest.config.js (a managed file: `starci app sync` renders it, `starci app check` compares it; do not edit)
|
|
@@ -24,7 +24,7 @@ and the exclusions. A repository has no other jest setting to tune.
|
|
|
24
24
|
(a framework registry such as `@nestjs/graphql`'s type metadata) reaches the file; and up to `min(--maxWorkers, slots)` files at
|
|
25
25
|
once, each bound to its own data slot of `@starci/test-world` 1.1.0 (`STARCI_TEST_WORLD_SLOT`), never two files on one slot,
|
|
26
26
|
because the test world resets a slot's data when a file boots. The runner reads test-world state protocol 2 only: pin
|
|
27
|
-
the two packages together (
|
|
27
|
+
the two packages together (`knowledge/stack/pins.yaml`); another protocol fails with `JEST_PRESET_WORLD_PAIR_MISMATCH`. Only the unit project has a `setupFilesAfterEnv`. The managed scripts select one
|
|
28
28
|
project each: `test`, `test:integration`, `test:e2e`, `test:contract`; a contract spec skips itself without sandbox config.
|
|
29
29
|
- **The three path aliases** `@features/*`, `@modules/*`, `@tests/*` (`MODULE_NAME_MAPPER`), the same ones the managed `tsconfig.json` declares.
|
|
30
30
|
- **ts-jest with `diagnostics: false`** in every project. `isolatedModules: true` comes from `@starci/tsconfig` (ts-jest 29.4 reads it
|
|
@@ -33,7 +33,7 @@ and the exclusions. A repository has no other jest setting to tune.
|
|
|
33
33
|
runs on code that does not type-check.
|
|
34
34
|
- **Coverage of the measured logic, per-file 100.** `collectCoverageFrom` is one glob per `coverage.roots` entry covering `<root>/**/*.{<roles>}.ts`
|
|
35
35
|
(the `ruleParams.be.logicRoles` of the rendered scope), minus its `excludes`, `dist` and `coverage`, and `coverageThreshold` holds
|
|
36
|
-
`{ lines: 100, branches: 100, functions: 100, statements: 100 }` on
|
|
36
|
+
`{ lines: 100, branches: 100, functions: 100, statements: 100 }` on one union glob of those roots. Jest applies a glob threshold to each file on its own, so every file of a
|
|
37
37
|
`ruleParams.be.logicRoles` role inside a measured slot must reach 100 on every metric (an average never passes). The
|
|
38
38
|
managed `test` script runs the unit project with `--coverage` and fails below it. Coverage is measured by v8, which counts the
|
|
39
39
|
real source. The unit project overlays `isolatedModules: true`, `importHelpers: true` and `sourceMap: true` on the repository tsconfig, so ts-jest
|
|
@@ -70,7 +70,7 @@ const service = moduleRef.get(CommissionService)
|
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
No `new` of the service, no `imports`, no `overrideProvider`, no `jest.mock`, no casts, no `Date.now()` and no `process.env`. The
|
|
73
|
-
full rules and the dependency-to-double-to-cases table are `
|
|
73
|
+
full rules and the dependency-to-double-to-cases table are `contexts/code/be/test.md` (the BE-TEST rules). Every
|
|
74
74
|
export below is on the package root (`@starci/jest-preset`); the sub-paths `./mock`, `./clock`, `./entity-manager`, `./ids`,
|
|
75
75
|
`./matchers`, `./cache`, `./lock`, `./event-bus`, `./queue`, `./inbox` and `./builders` exist as well.
|
|
76
76
|
|
|
@@ -141,8 +141,21 @@ await inbox.claim("sepay", "e-1") // true the first time, false for the s
|
|
|
141
141
|
await inbox.release("sepay", "e-1") // gives the claim back: the next claim answers true
|
|
142
142
|
inbox.seen("sepay", "e-2") // a redelivery: the next claim of the pair answers false
|
|
143
143
|
inbox.claims; inbox.claimed; inbox.released; inbox.failNext("claim", new Error("db down")); inbox.clear()
|
|
144
|
+
|
|
145
|
+
const clientInbox = fakeInbox(clock) // the driven clock stamps client claims for retention tests
|
|
146
|
+
await clientInbox.runClient({ principalId: "p-1", operation: "checkout.place", idempotencyKey: "key-1", request: { quantity: 2 } }, tx, async () => ({ kind: "ok", value: { orderId: "o-1" } }))
|
|
147
|
+
clientInbox.clientEntries // every client claim with its tx and inTransaction flag
|
|
148
|
+
await clientInbox.sweepClients(cutoff, tx)
|
|
149
|
+
clientInbox.sweeps // every sweep attempt's cutoff, tx and inTransaction flag, failures included
|
|
144
150
|
```
|
|
145
151
|
|
|
152
|
+
Client repeats replay the stored JSON outcome, including a refusal; a different request for the same namespace and key
|
|
153
|
+
rejects with `IDEMPOTENCY_KEY_REUSED`. Concurrent repeats wait for the first effect. A rejected effect gives its claim back.
|
|
154
|
+
Delivery `release` preserves client receipts and their outcomes.
|
|
155
|
+
The double records the transaction passed to it; a later outer transaction rollback needs real database integration proof.
|
|
156
|
+
Without a clock, client claims use the Unix epoch. The sweep removes one bounded batch of completed expired client claims
|
|
157
|
+
and preserves delivery claims.
|
|
158
|
+
|
|
146
159
|
## `builder`, `fakeIds` and the Outcome matchers
|
|
147
160
|
|
|
148
161
|
```ts
|
package/entity-manager.cjs
CHANGED
|
@@ -85,6 +85,7 @@ function createEntityManagerKit(makeFn) {
|
|
|
85
85
|
const scopedFor = (run) => {
|
|
86
86
|
const view = new Proxy(em, {
|
|
87
87
|
get(target, property, receiver) {
|
|
88
|
+
if (property === "queryRunner") return { get isTransactionActive() { return run.outcome === "pending" } }
|
|
88
89
|
const value = Reflect.get(target, property, receiver)
|
|
89
90
|
if (property === "transaction") return (...args) => args[args.length - 1](view)
|
|
90
91
|
if (typeof property !== "string" || typeof value !== "function") return value
|
package/entity-manager.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ export interface FakeTransaction<M extends MockEntityManager = MockEntityManager
|
|
|
90
90
|
readonly rolledBackWrites: ReadonlyArray<TransactionWrite>
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/** Makes `em.transaction(work)` run `work` with a scoped view
|
|
93
|
+
/** Makes `em.transaction(work)` run `work` with a scoped view whose queryRunner stays active until commit or rollback. */
|
|
94
94
|
export declare function fakeTransaction<M extends MockEntityManager = MockEntityManager>(em?: M): FakeTransaction<M>
|
|
95
95
|
|
|
96
96
|
/** Whether `manager` is the view a `fakeTransaction` body received (used by `recordingEventBus` and `recordingQueueOutbox`). */
|
package/inbox.cjs
CHANGED
|
@@ -1,19 +1,46 @@
|
|
|
1
1
|
"use strict"
|
|
2
2
|
|
|
3
|
+
const { isDeepStrictEqual } = require("node:util")
|
|
4
|
+
const { isTransactionManager } = require("./entity-manager.cjs")
|
|
5
|
+
const CLIENT_SWEEP_BATCH = 500
|
|
6
|
+
|
|
7
|
+
/** The fake carries the production refusal code without depending on an application's error family. */
|
|
8
|
+
const inboxError = (code) => Object.assign(new Error(code), { code })
|
|
9
|
+
|
|
10
|
+
/** Clone JSON values directly so toJSON cannot change the request or the stored outcome. */
|
|
11
|
+
const snapshot = (value, ancestors = new Set()) => {
|
|
12
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value
|
|
13
|
+
if (typeof value === "number" && Number.isFinite(value)) return value === 0 ? 0 : value
|
|
14
|
+
if (typeof value !== "object" || ancestors.has(value)) throw inboxError("IDEMPOTENCY_REQUEST_INVALID")
|
|
15
|
+
ancestors.add(value)
|
|
16
|
+
let result
|
|
17
|
+
if (Array.isArray(value)) result = Array.from(value, (member) => snapshot(member, ancestors))
|
|
18
|
+
else {
|
|
19
|
+
const prototype = Object.getPrototypeOf(value)
|
|
20
|
+
if (prototype !== Object.prototype && prototype !== null) throw inboxError("IDEMPOTENCY_REQUEST_INVALID")
|
|
21
|
+
result = Object.fromEntries(Object.entries(value).map(([key, member]) => [key, snapshot(member, ancestors)]))
|
|
22
|
+
}
|
|
23
|
+
ancestors.delete(value)
|
|
24
|
+
return result
|
|
25
|
+
}
|
|
26
|
+
|
|
3
27
|
/**
|
|
4
|
-
* `fakeInbox()`
|
|
5
|
-
*
|
|
28
|
+
* `fakeInbox(clock?)` records delivery and client claims, so a spec asserts deduplication and the transaction passed by
|
|
29
|
+
* its service. Client calls replay JSON outcomes; `clock` gives retention tests a driven claim timestamp.
|
|
6
30
|
*
|
|
7
31
|
* `claim(source, eventId)` answers `true` for the first call of a (source, eventId) pair and `false` for every later one, like the
|
|
8
|
-
* real unique key; `release(source, eventId)` gives
|
|
32
|
+
* real unique key; `release(source, eventId)` gives an ordinary delivery claim back while preserving client outcomes.
|
|
9
33
|
* `claims` is every claim in order, `claimed` the pairs that are held now, `released` every release, `seen(source, eventId)` marks a
|
|
10
34
|
* pair as already delivered (a redelivery), and `failNext(operation, error)` makes the next call of that operation reject once, so
|
|
11
35
|
* a spec drives the failure path.
|
|
12
36
|
*/
|
|
13
|
-
function fakeInbox() {
|
|
37
|
+
function fakeInbox(clock) {
|
|
14
38
|
const held = new Set()
|
|
15
39
|
const claims = []
|
|
16
40
|
const released = []
|
|
41
|
+
const clients = new Map()
|
|
42
|
+
const clientEntries = []
|
|
43
|
+
const sweeps = []
|
|
17
44
|
const failures = new Map()
|
|
18
45
|
const keyOf = (source, eventId) => `${source}\u0000${eventId}`
|
|
19
46
|
const failIfScripted = (operation) => {
|
|
@@ -34,7 +61,60 @@ function fakeInbox() {
|
|
|
34
61
|
async release(source, eventId) {
|
|
35
62
|
failIfScripted("release")
|
|
36
63
|
released.push({ source, eventId })
|
|
37
|
-
|
|
64
|
+
const key = keyOf(source, eventId)
|
|
65
|
+
if (!clients.has(key)) held.delete(key)
|
|
66
|
+
},
|
|
67
|
+
async runClient(request, tx, effect) {
|
|
68
|
+
failIfScripted("runClient")
|
|
69
|
+
const { principalId, operation, idempotencyKey } = request
|
|
70
|
+
if (!principalId.trim() || principalId.includes(":") || !operation.trim() || operation.includes(":")
|
|
71
|
+
|| !idempotencyKey.trim() || idempotencyKey.length > 64) {
|
|
72
|
+
throw inboxError("IDEMPOTENCY_REQUEST_INVALID")
|
|
73
|
+
}
|
|
74
|
+
const source = `client:${principalId}:${operation}`
|
|
75
|
+
const key = keyOf(source, idempotencyKey)
|
|
76
|
+
const body = snapshot(request.request)
|
|
77
|
+
claims.push({ source, eventId: idempotencyKey })
|
|
78
|
+
clientEntries.push({ source, eventId: idempotencyKey, tx, inTransaction: isTransactionManager(tx) })
|
|
79
|
+
const previous = clients.get(key)
|
|
80
|
+
if (previous !== undefined) {
|
|
81
|
+
if (!isDeepStrictEqual(previous.request, body)) throw inboxError("IDEMPOTENCY_KEY_REUSED")
|
|
82
|
+
return snapshot(await previous.result)
|
|
83
|
+
}
|
|
84
|
+
if (held.has(key)) throw inboxError("IDEMPOTENCY_KEY_REUSED")
|
|
85
|
+
const entry = { request: body, claimedAt: clock === undefined ? new Date(0) : clock.now(), completed: false }
|
|
86
|
+
held.add(key)
|
|
87
|
+
clients.set(key, entry)
|
|
88
|
+
entry.result = Promise.resolve().then(effect).then((value) => {
|
|
89
|
+
let stored
|
|
90
|
+
try {
|
|
91
|
+
stored = snapshot(value)
|
|
92
|
+
} catch (cause) {
|
|
93
|
+
throw Object.assign(inboxError("INBOX_OUTCOME_INVALID"), { cause })
|
|
94
|
+
}
|
|
95
|
+
entry.completed = true
|
|
96
|
+
return stored
|
|
97
|
+
}).catch((error) => {
|
|
98
|
+
if (clients.get(key) === entry) {
|
|
99
|
+
clients.delete(key)
|
|
100
|
+
held.delete(key)
|
|
101
|
+
}
|
|
102
|
+
throw error
|
|
103
|
+
})
|
|
104
|
+
return snapshot(await entry.result)
|
|
105
|
+
},
|
|
106
|
+
async sweepClients(cutoff, tx) {
|
|
107
|
+
sweeps.push({ cutoff: new Date(cutoff), tx, inTransaction: isTransactionManager(tx) })
|
|
108
|
+
failIfScripted("sweepClients")
|
|
109
|
+
let swept = 0
|
|
110
|
+
for (const [key, entry] of clients) {
|
|
111
|
+
if (swept === CLIENT_SWEEP_BATCH) break
|
|
112
|
+
if (!entry.completed || entry.claimedAt >= cutoff) continue
|
|
113
|
+
clients.delete(key)
|
|
114
|
+
held.delete(key)
|
|
115
|
+
swept += 1
|
|
116
|
+
}
|
|
117
|
+
return swept
|
|
38
118
|
},
|
|
39
119
|
/** Marks a pair as already claimed, so the next `claim` of it answers `false` (a redelivery). */
|
|
40
120
|
seen(source, eventId) {
|
|
@@ -59,11 +139,22 @@ function fakeInbox() {
|
|
|
59
139
|
get released() {
|
|
60
140
|
return [...released]
|
|
61
141
|
},
|
|
142
|
+
/** Every client claim, including repeats, with the domain transaction it used. */
|
|
143
|
+
get clientEntries() {
|
|
144
|
+
return [...clientEntries]
|
|
145
|
+
},
|
|
146
|
+
/** Every retention attempt with its cutoff and domain transaction, including a scripted failure. */
|
|
147
|
+
get sweeps() {
|
|
148
|
+
return [...sweeps]
|
|
149
|
+
},
|
|
62
150
|
clear() {
|
|
63
151
|
held.clear()
|
|
64
152
|
claims.length = 0
|
|
65
153
|
released.length = 0
|
|
66
154
|
failures.clear()
|
|
155
|
+
clients.clear()
|
|
156
|
+
clientEntries.length = 0
|
|
157
|
+
sweeps.length = 0
|
|
67
158
|
},
|
|
68
159
|
}
|
|
69
160
|
}
|
package/inbox.d.ts
CHANGED
|
@@ -4,14 +4,39 @@ export interface InboxEntry {
|
|
|
4
4
|
readonly eventId: string
|
|
5
5
|
}
|
|
6
6
|
/** The inbox operations `failNext` can make reject. */
|
|
7
|
-
export type InboxOperation = "claim" | "release"
|
|
7
|
+
export type InboxOperation = "claim" | "release" | "runClient" | "sweepClients"
|
|
8
|
+
|
|
9
|
+
/** The client operation namespace, exact key and JSON request whose outcome the inbox stores. */
|
|
10
|
+
export interface InboxClientOperation {
|
|
11
|
+
readonly principalId: string
|
|
12
|
+
readonly operation: string
|
|
13
|
+
readonly idempotencyKey: string
|
|
14
|
+
readonly request: unknown
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A client claim with the manager passed by its caller. */
|
|
18
|
+
export interface InboxClientEntry extends InboxEntry {
|
|
19
|
+
readonly tx: unknown
|
|
20
|
+
readonly inTransaction: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One retention sweep with the cutoff instant and manager passed by its caller. */
|
|
24
|
+
export interface InboxSweep {
|
|
25
|
+
readonly cutoff: Date
|
|
26
|
+
readonly tx: unknown
|
|
27
|
+
readonly inTransaction: boolean
|
|
28
|
+
}
|
|
8
29
|
|
|
9
30
|
/** An `Inbox` double with the behaviour of the real claim. */
|
|
10
31
|
export interface FakeInbox {
|
|
11
32
|
/** True for the first claim of a (source, eventId) pair, false for every later one until it is released. */
|
|
12
33
|
claim(source: string, eventId: string): Promise<boolean>
|
|
13
|
-
/** Gives
|
|
34
|
+
/** Gives an ordinary delivery claim back while preserving client receipts and their outcomes. */
|
|
14
35
|
release(source: string, eventId: string): Promise<void>
|
|
36
|
+
/** Runs once for the client namespace and key, replays the JSON outcome and refuses a different body. */
|
|
37
|
+
runClient<T>(request: InboxClientOperation, tx: unknown, effect: () => Promise<T>): Promise<T>
|
|
38
|
+
/** Deletes at most one batch of completed client claims older than the cutoff. */
|
|
39
|
+
sweepClients(cutoff: Date, tx: unknown): Promise<number>
|
|
15
40
|
/** Marks a pair as already claimed (a redelivery). */
|
|
16
41
|
seen(source: string, eventId: string): void
|
|
17
42
|
/** Makes the next call of `operation` reject with `error`, once. */
|
|
@@ -22,8 +47,12 @@ export interface FakeInbox {
|
|
|
22
47
|
readonly claimed: ReadonlyArray<InboxEntry>
|
|
23
48
|
/** Every release, in order. */
|
|
24
49
|
readonly released: ReadonlyArray<InboxEntry>
|
|
50
|
+
/** Every client claim with its manager; this double records transaction ownership, not database commit semantics. */
|
|
51
|
+
readonly clientEntries: ReadonlyArray<InboxClientEntry>
|
|
52
|
+
/** Every sweep attempt with its cutoff and transaction, including a scripted failure. */
|
|
53
|
+
readonly sweeps: ReadonlyArray<InboxSweep>
|
|
25
54
|
clear(): void
|
|
26
55
|
}
|
|
27
56
|
|
|
28
|
-
/** The inbox double. */
|
|
29
|
-
export declare function fakeInbox(): FakeInbox
|
|
57
|
+
/** The inbox double. Pass a driven clock to test retention; omitted clocks stamp client claims at the Unix epoch. */
|
|
58
|
+
export declare function fakeInbox(clock?: { now(): Date }): FakeInbox
|
package/index.cjs
CHANGED
|
@@ -37,6 +37,14 @@ function collectCoverageFrom(coverage) {
|
|
|
37
37
|
return [...coverage.roots.map((root) => rootGlob(root, coverage.roles)), ...[...coverage.excludes, ...COVERAGE_EXCLUDES].map((glob) => `!${glob}`)]
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** One union glob keeps Jest's per-file checks while a scoped map may contain files from only one measured root. */
|
|
41
|
+
function coverageThresholdOf(coverage, root) {
|
|
42
|
+
const rootGlobs = coverage.roots.map((entry) => rootGlob(entry, coverage.roles)).filter((glob) => hasCoverageSubjects(root, glob))
|
|
43
|
+
if (rootGlobs.length === 0) return {}
|
|
44
|
+
const glob = rootGlobs.length === 1 ? rootGlobs[0] : `{${rootGlobs.join(",")}}`
|
|
45
|
+
return { [`./${glob}`]: { ...COVERAGE_THRESHOLD } }
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
/**
|
|
41
49
|
* Sonar `sonar.exclusions`: specs, e2e specs, dist and coverage output are not analysed at all. The list lives in sonar-exclusions.cjs, a
|
|
42
50
|
* require-free module @starci/hfs also ships byte for byte (packages/hfs/runtime, scripts/hfs/sync-runtime.mjs), so `starci app scaffold`
|
|
@@ -236,7 +244,7 @@ function starciJestConfig(options) {
|
|
|
236
244
|
...(shard ? {} : {
|
|
237
245
|
collectCoverageFrom: collectCoverageFrom(coverage),
|
|
238
246
|
// A glob key is applied to every matching file on its own: each measured file must reach 100, not the average.
|
|
239
|
-
coverageThreshold:
|
|
247
|
+
coverageThreshold: coverageThresholdOf(coverage, root),
|
|
240
248
|
}),
|
|
241
249
|
coverageDirectory: "coverage",
|
|
242
250
|
// lcov is what Sonar imports (coverage/lcov.info); text-summary and text are what the developer reads; a shard writes the json the merge reads.
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starci/jest-preset",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.7",
|
|
4
4
|
"description": "The StarCi jest config for Nest repositories (unit, integration, e2e and contract projects, ts-jest with diagnostics off, per-file 100 coverage of *.service.ts only) and the one shared unit-test double kit: typed mock<T>(), mockEntityManager(), fakeTransaction(), fakeCache(), fakeLock(), recordingEventBus(), recordingQueueOutbox(), fakeInbox(), FakeClock, fakeIds(), builder() and the Outcome matchers toBeRefused/toSucceedWith.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": "starci183",
|
|
7
|
+
"homepage": "https://github.com/starci183/starci-harness/tree/main/packages/jest-preset#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/starci183/starci-harness.git",
|
|
11
|
+
"directory": "packages/jest-preset"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/starci183/starci-harness/issues"
|
|
15
|
+
},
|
|
6
16
|
"private": false,
|
|
7
17
|
"main": "index.cjs",
|
|
8
18
|
"types": "index.d.ts",
|