@starci/jest-preset 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.5.0 - 2026-10-29
4
+
5
+ - Added: `fakeQueryBuilder(answers)` and `queueQueryBuilders(em, answers)` (also at `@starci/jest-preset/query-builder`), the double of `createQueryBuilder(Entity, "alias")` beside `mockEntityManager`: every chained call is recorded and returned to the chain, the terminals resolve the spec's answers. Lite persistence reads through entities and that builder (ruling `lite-persistence-typeorm-entities`), so the double moves here from the apps.
6
+
3
7
  ## 2.4.1 - 2026-10-28
4
8
 
5
9
  - Changed: `@starci/ai-router` joins the ESM-only dependencies jest compiles to CommonJS, so a spec that builds an AppModule registering the AI pool loads it.
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 the StarCi runtime's `knowledge/stack/pins.yaml` (see [`packages/README.md`](../README.md)).
3
+ The jest config of a Nest repository and the one shared unit-test double kit (`mock<T>()`, `mockEntityManager`, `fakeTransaction`, `fakeQueryBuilder`, `queueQueryBuilders`, `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)
@@ -72,7 +72,7 @@ const service = moduleRef.get(CommissionService)
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
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
- `./matchers`, `./cache`, `./lock`, `./event-bus`, `./queue`, `./inbox` and `./builders` exist as well.
75
+ `./query-builder`, `./matchers`, `./cache`, `./lock`, `./event-bus`, `./queue`, `./inbox` and `./builders` exist as well.
76
76
 
77
77
  ## `mockEntityManager` and `fakeTransaction`
78
78
 
@@ -105,6 +105,21 @@ The stubbable methods are `findOne`, `findOneBy`, `findOneOrFail`, `findOneByOrF
105
105
  `delete`, `softDelete`, `restore`, `increment`, `decrement` and `query`. `fakeTransaction(em)` returns `{ em, outcomes, commits,
106
106
  rollbacks, committedWrites, rolledBackWrites }`.
107
107
 
108
+ ## `fakeQueryBuilder` and `queueQueryBuilders`
109
+
110
+ The double of `createQueryBuilder(Entity, "alias")`, the builder a lite service reads with. Every chained method records
111
+ `{ method, args }` and returns the builder; a terminal (`execute`, `getMany`, `getOne`, `getRawMany`, `getRawOne`, `getCount`,
112
+ `getManyAndCount`) resolves the answer the spec gave it, and a function answer is called when the terminal runs.
113
+
114
+ ```ts
115
+ import { mockEntityManager, queueQueryBuilders } from "@starci/jest-preset"
116
+
117
+ const em = mockEntityManager()
118
+ const [open] = queueQueryBuilders(em, [{ getMany: [order] }]) // one fake per createQueryBuilder call, in order
119
+ await service.listOpen(owner)
120
+ expect(open.callsOf("where")).toEqual([["o.ownerId = :owner", { owner }]])
121
+ ```
122
+
108
123
  ## `fakeCache(clock)`, `fakeLock(clock)`, `recordingEventBus()` and `recordingQueueOutbox()`
109
124
 
110
125
  ```ts
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Config } from "jest";
2
2
  import { mock, createMock } from "./mock.js";
3
3
  import { mockEntityManager, fakeTransaction } from "./entity-manager.js";
4
+ import { fakeQueryBuilder, queueQueryBuilders } from "./query-builder.js";
4
5
  import { FakeClock } from "./clock.js";
5
6
  import { fakeCache } from "./cache.js";
6
7
  import { fakeLock } from "./lock.js";
@@ -11,6 +12,7 @@ import { builder } from "./builders.js";
11
12
  import { fakeIds, FakeIds } from "./ids.js";
12
13
  export type { MockOf } from "./mock.js";
13
14
  export type { MockEntityManager, FakeTransaction, EntityClass } from "./entity-manager.js";
15
+ export type { FakeQueryBuilder, QueryBuilderAnswers, QueryBuilderCall, QueryBuilderHost } from "./query-builder.js";
14
16
  export type { FakeCache } from "./cache.js";
15
17
  export type { FakeLock } from "./lock.js";
16
18
  export type { RecordingEventBus } from "./event-bus.js";
@@ -111,4 +113,4 @@ declare const WORLD_RUNNER: string;
111
113
  declare function hasCoverageSubjects(root: string, glob: string): boolean;
112
114
  /** The whole jest config: projects `unit`, `integration`, `e2e` and `contract`. It takes the coverage scope the managed repository config renders; without it it refuses. */
113
115
  declare function starciJestConfig(options: StarciJestOptions): StarciJestConfig;
114
- export { starciJestConfig, TEST_KIND_FOLDERS, MODULE_NAME_MAPPER, mock, createMock, mockEntityManager, fakeTransaction, FakeClock, fakeCache, fakeLock, recordingEventBus, recordingQueueOutbox, fakeInbox, builder, fakeIds, FakeIds, collectCoverageFrom, rootGlob, sonarExclusions, hasCoverageSubjects, COVERAGE_EXCLUDES, COVERAGE_THRESHOLD, UNIT_COMPILER_OPTIONS, WORLD_RUNNER, };
116
+ export { starciJestConfig, TEST_KIND_FOLDERS, MODULE_NAME_MAPPER, mock, createMock, mockEntityManager, fakeTransaction, fakeQueryBuilder, queueQueryBuilders, FakeClock, fakeCache, fakeLock, recordingEventBus, recordingQueueOutbox, fakeInbox, builder, fakeIds, FakeIds, collectCoverageFrom, rootGlob, sonarExclusions, hasCoverageSubjects, COVERAGE_EXCLUDES, COVERAGE_THRESHOLD, UNIT_COMPILER_OPTIONS, WORLD_RUNNER, };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WORLD_RUNNER = exports.UNIT_COMPILER_OPTIONS = exports.COVERAGE_THRESHOLD = exports.COVERAGE_EXCLUDES = exports.FakeIds = exports.fakeIds = exports.builder = exports.fakeInbox = exports.recordingQueueOutbox = exports.recordingEventBus = exports.fakeLock = exports.fakeCache = exports.FakeClock = exports.fakeTransaction = exports.mockEntityManager = exports.createMock = exports.mock = exports.MODULE_NAME_MAPPER = exports.TEST_KIND_FOLDERS = void 0;
3
+ exports.WORLD_RUNNER = exports.UNIT_COMPILER_OPTIONS = exports.COVERAGE_THRESHOLD = exports.COVERAGE_EXCLUDES = exports.FakeIds = exports.fakeIds = exports.builder = exports.fakeInbox = exports.recordingQueueOutbox = exports.recordingEventBus = exports.fakeLock = exports.fakeCache = exports.FakeClock = exports.queueQueryBuilders = exports.fakeQueryBuilder = exports.fakeTransaction = exports.mockEntityManager = exports.createMock = exports.mock = exports.MODULE_NAME_MAPPER = exports.TEST_KIND_FOLDERS = void 0;
4
4
  exports.starciJestConfig = starciJestConfig;
5
5
  exports.collectCoverageFrom = collectCoverageFrom;
6
6
  exports.rootGlob = rootGlob;
@@ -14,6 +14,9 @@ Object.defineProperty(exports, "createMock", { enumerable: true, get: function (
14
14
  const entity_manager_ts_1 = require("./entity-manager.js");
15
15
  Object.defineProperty(exports, "mockEntityManager", { enumerable: true, get: function () { return entity_manager_ts_1.mockEntityManager; } });
16
16
  Object.defineProperty(exports, "fakeTransaction", { enumerable: true, get: function () { return entity_manager_ts_1.fakeTransaction; } });
17
+ const query_builder_ts_1 = require("./query-builder.js");
18
+ Object.defineProperty(exports, "fakeQueryBuilder", { enumerable: true, get: function () { return query_builder_ts_1.fakeQueryBuilder; } });
19
+ Object.defineProperty(exports, "queueQueryBuilders", { enumerable: true, get: function () { return query_builder_ts_1.queueQueryBuilders; } });
17
20
  const clock_ts_1 = require("./clock.js");
18
21
  Object.defineProperty(exports, "FakeClock", { enumerable: true, get: function () { return clock_ts_1.FakeClock; } });
19
22
  const cache_ts_1 = require("./cache.js");
@@ -0,0 +1,56 @@
1
+ /**
2
+ * `fakeQueryBuilder()` and `queueQueryBuilders()` -- the double of a typeorm `SelectQueryBuilder` (and its insert, update
3
+ * and delete siblings) that `mockEntityManager` hands out from `createQueryBuilder(Entity, "alias")`.
4
+ *
5
+ * A lite service reads with `createQueryBuilder(Entity, "alias")` and named parameters (persistence.md BE-PERSISTENCE-4).
6
+ * The fake is a Proxy: every chained method (`where`, `andWhere`, `innerJoin`, `orderBy`, `take`, `setLock`, `returning`,
7
+ * ...) records `{ method, args }` and returns the builder; a terminal method (`execute`, `getMany`, `getOne`,
8
+ * `getRawMany`, `getRawOne`, `getCount`, `getManyAndCount`) records too and resolves the answer the spec gave it (a function
9
+ * answer is called when the terminal runs). A spec asserts the statement it expects through `calls` or `callsOf(method)`.
10
+ *
11
+ * `queueQueryBuilders(em, answers)` queues one fake per answer on `em.createQueryBuilder` of a `mockEntityManager()`, in
12
+ * call order, and returns the fakes so the spec can read each one's calls.
13
+ */
14
+ /** One chained call recorded on a fake query builder. */
15
+ export interface QueryBuilderCall {
16
+ /** The builder method that was called. */
17
+ readonly method: string;
18
+ /** The arguments it was called with. */
19
+ readonly args: ReadonlyArray<unknown>;
20
+ }
21
+ /** What the terminal methods of a fake query builder answer; a function is called when the terminal runs and its result returned. */
22
+ export interface QueryBuilderAnswers {
23
+ /** The answer of `execute()`. */
24
+ readonly execute?: unknown;
25
+ /** The answer of `getMany()`. */
26
+ readonly getMany?: ReadonlyArray<unknown> | (() => unknown);
27
+ /** The answer of `getOne()`. */
28
+ readonly getOne?: unknown;
29
+ /** The answer of `getRawMany()`. */
30
+ readonly getRawMany?: ReadonlyArray<unknown> | (() => unknown);
31
+ /** The answer of `getRawOne()`. */
32
+ readonly getRawOne?: unknown;
33
+ /** The answer of `getCount()`. */
34
+ readonly getCount?: number | (() => unknown);
35
+ /** The answer of `getManyAndCount()`. */
36
+ readonly getManyAndCount?: readonly [ReadonlyArray<unknown>, number] | (() => unknown);
37
+ }
38
+ /** A chainable stand-in for a typeorm query builder that records every chained call. */
39
+ export interface FakeQueryBuilder {
40
+ /** The builder to hand back from `entityManager.createQueryBuilder`. */
41
+ readonly builder: object;
42
+ /** Every chained call, in order. */
43
+ readonly calls: ReadonlyArray<QueryBuilderCall>;
44
+ /** The argument lists of every call of one builder method, in order. */
45
+ callsOf(method: string): ReadonlyArray<ReadonlyArray<unknown>>;
46
+ }
47
+ /** Builds a chainable query-builder double: every method returns the builder, the terminal methods resolve `answers`. */
48
+ export declare const fakeQueryBuilder: (answers?: QueryBuilderAnswers) => FakeQueryBuilder;
49
+ /** The part of a `mockEntityManager()` this helper queues on: its `createQueryBuilder` jest mock. */
50
+ export interface QueryBuilderHost {
51
+ readonly createQueryBuilder: {
52
+ mockReturnValueOnce(value: never): unknown;
53
+ };
54
+ }
55
+ /** Queues one fake query builder per answer on `manager.createQueryBuilder`, handed out in call order, and returns them. */
56
+ export declare const queueQueryBuilders: (manager: QueryBuilderHost, answers: ReadonlyArray<QueryBuilderAnswers>) => ReadonlyArray<FakeQueryBuilder>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * `fakeQueryBuilder()` and `queueQueryBuilders()` -- the double of a typeorm `SelectQueryBuilder` (and its insert, update
4
+ * and delete siblings) that `mockEntityManager` hands out from `createQueryBuilder(Entity, "alias")`.
5
+ *
6
+ * A lite service reads with `createQueryBuilder(Entity, "alias")` and named parameters (persistence.md BE-PERSISTENCE-4).
7
+ * The fake is a Proxy: every chained method (`where`, `andWhere`, `innerJoin`, `orderBy`, `take`, `setLock`, `returning`,
8
+ * ...) records `{ method, args }` and returns the builder; a terminal method (`execute`, `getMany`, `getOne`,
9
+ * `getRawMany`, `getRawOne`, `getCount`, `getManyAndCount`) records too and resolves the answer the spec gave it (a function
10
+ * answer is called when the terminal runs). A spec asserts the statement it expects through `calls` or `callsOf(method)`.
11
+ *
12
+ * `queueQueryBuilders(em, answers)` queues one fake per answer on `em.createQueryBuilder` of a `mockEntityManager()`, in
13
+ * call order, and returns the fakes so the spec can read each one's calls.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.queueQueryBuilders = exports.fakeQueryBuilder = void 0;
17
+ /** The methods that end a chain and resolve the spec's answer. */
18
+ const TERMINALS = ["execute", "getMany", "getOne", "getRawMany", "getRawOne", "getCount", "getManyAndCount"];
19
+ const isTerminal = (method) => TERMINALS.some((name) => name === method);
20
+ /** Builds a chainable query-builder double: every method returns the builder, the terminal methods resolve `answers`. */
21
+ const fakeQueryBuilder = (answers = {}) => {
22
+ const calls = [];
23
+ const builder = new Proxy({}, {
24
+ get: (_target, property) => {
25
+ // `then` stays undefined so an awaited builder is not mistaken for a promise.
26
+ if (typeof property !== "string" || property === "then")
27
+ return undefined;
28
+ return (...args) => {
29
+ calls.push({ method: property, args });
30
+ if (!isTerminal(property))
31
+ return builder;
32
+ const answer = answers[property];
33
+ return Promise.resolve(typeof answer === "function" ? answer() : answer);
34
+ };
35
+ },
36
+ });
37
+ return {
38
+ builder,
39
+ calls,
40
+ callsOf: (method) => calls.filter((call) => call.method === method).map((call) => call.args),
41
+ };
42
+ };
43
+ exports.fakeQueryBuilder = fakeQueryBuilder;
44
+ /** Queues one fake query builder per answer on `manager.createQueryBuilder`, handed out in call order, and returns them. */
45
+ const queueQueryBuilders = (manager, answers) => {
46
+ const fakes = answers.map((answer) => (0, exports.fakeQueryBuilder)(answer));
47
+ for (const fake of fakes)
48
+ manager.createQueryBuilder.mockReturnValueOnce(fake.builder);
49
+ return fakes;
50
+ };
51
+ exports.queueQueryBuilders = queueQueryBuilders;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@starci/jest-preset",
3
- "version": "2.4.1",
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.",
3
+ "version": "2.5.0",
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(), fakeQueryBuilder(), queueQueryBuilders(), fakeCache(), fakeLock(), recordingEventBus(), recordingQueueOutbox(), fakeInbox(), FakeClock, fakeIds(), builder() and the Outcome matchers toBeRefused/toSucceedWith.",
5
5
  "license": "MIT",
6
6
  "author": "starci183",
7
7
  "homepage": "https://github.com/starci183/starci-harness/tree/main/packages/jest-preset#readme",
@@ -34,6 +34,10 @@
34
34
  "types": "./dist/entity-manager.d.ts",
35
35
  "default": "./dist/entity-manager.js"
36
36
  },
37
+ "./query-builder": {
38
+ "types": "./dist/query-builder.d.ts",
39
+ "default": "./dist/query-builder.js"
40
+ },
37
41
  "./ids": {
38
42
  "types": "./dist/ids.d.ts",
39
43
  "default": "./dist/ids.js"