@vttforge/testing 0.2.0 → 0.3.1

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,33 @@
1
1
  # @vttforge/testing
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 7eeeb20: Rewrite the npm package descriptions to say what each package does today. `types` claimed full schema inference it does not have, `vite-plugin` claimed Handlebars HMR that lives in the dev loop, and `cli` did not mention `audit`.
8
+
9
+ ## 0.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - ae8dafb: `withMockFoundry` records sheets and enrichers.
14
+
15
+ Registering a sheet through `registerSystem({ sheets })` goes via `foundry.applications.apps.DocumentSheetConfig`, which the mock did not have — so a consumer's boot test got "Foundry is not available" rather than a result. Enrichers landed on `CONFIG.TextEditor.enrichers` with no way to read them back.
16
+
17
+ Both are now on the handle:
18
+
19
+ ```ts
20
+ const foundry = withMockFoundry();
21
+ registerSystem({ id: 'my-system', sheets: [{ id: 'character', document: 'Actor', sheet: CharacterSheet }] });
22
+ foundry.callHook('init');
23
+
24
+ foundry.sheets.map((s) => s.key); // ['my-system.character']
25
+ ```
26
+
27
+ `key` is the assertion worth writing. Foundry saves it on every document whose owner picked the sheet and builds it from the class name, which a bundler is free to rename between builds — so pinning the key is pinning that the reader's choice survives your next release.
28
+
29
+ `foundry.enrichers` reads back the namespaced id, pattern and `onRender`.
30
+
3
31
  ## 0.2.0
4
32
 
5
33
  ### Minor Changes
package/README.md CHANGED
@@ -23,20 +23,20 @@ foundry.restore();
23
23
  ```
24
24
 
25
25
  `withMockFoundry` installs `foundry`, `game`, `CONFIG`, `Hooks`, `ui` and
26
- `CONST`, and hands back a handle that records what your code registered hooks,
27
- settings, notifications so a test can assert on what happened rather than only
26
+ `CONST`, and hands back a handle that records what your code registered (hooks,
27
+ settings, notifications) so a test can assert on what happened rather than only
28
28
  on what did not throw. `restore()` puts every global back, including deleting the
29
29
  ones that never existed.
30
30
 
31
31
  The mock documents behave like real ones where it counts: `update` merges rather
32
- than replacing, and dotted paths expand. Both matter a mock that replaces lets
32
+ than replacing, and dotted paths expand. Both matter: a mock that replaces lets
33
33
  a test pass while the real thing drops every sibling key.
34
34
 
35
35
  ### Naming the globals
36
36
 
37
37
  A test that reads `game.settings` would otherwise get "Cannot find name
38
38
  'game'". Importing from this entry declares them, so there is nothing to
39
- configure the import a test already writes is what brings them.
39
+ configure. The import a test already writes is what brings them.
40
40
 
41
41
  ## `@vttforge/testing/quench`
42
42
 
@@ -62,6 +62,25 @@ interface RecordedSetting {
62
62
  key: string;
63
63
  config: Record<string, unknown>;
64
64
  }
65
+ /** One `DocumentSheetConfig.registerSheet`, as VTTForge makes it. */
66
+ interface RecordedSheet {
67
+ /** The key Foundry persists: `<package id>.<sheet id>`. */
68
+ readonly key: string;
69
+ /** The package that registered it. */
70
+ readonly scope: string;
71
+ /** The sheet id — the class name VTTForge pinned. */
72
+ readonly id: string;
73
+ readonly sheetClass: unknown;
74
+ readonly documentClass: unknown;
75
+ readonly options: Record<string, unknown>;
76
+ }
77
+ /** One entry pushed onto `CONFIG.TextEditor.enrichers`. */
78
+ interface RecordedEnricher {
79
+ /** The namespaced id: `<package id>.<enricher id>`. */
80
+ readonly id: string;
81
+ readonly pattern: RegExp;
82
+ readonly onRender?: unknown;
83
+ }
65
84
  interface MockFoundry {
66
85
  /** Every `Hooks.on` and `Hooks.once`, in order. */
67
86
  readonly hooks: ReadonlyArray<RecordedHook>;
@@ -72,6 +91,21 @@ interface MockFoundry {
72
91
  level: 'info' | 'warn' | 'error';
73
92
  message: string;
74
93
  }>;
94
+ /**
95
+ * Every sheet registered, in order.
96
+ *
97
+ * `key` is the thing worth asserting: Foundry saves it on each document
98
+ * using the sheet, so a test that pins the key is a test that the reader's
99
+ * choice survives your next build.
100
+ */
101
+ readonly sheets: ReadonlyArray<RecordedSheet>;
102
+ /**
103
+ * Every text enricher registered, in order.
104
+ *
105
+ * `id` is namespaced, which is what stops a common name from colliding with
106
+ * another package — and what makes `onRender` fire at all.
107
+ */
108
+ readonly enrichers: ReadonlyArray<RecordedEnricher>;
75
109
  /** Fire a hook the way Foundry would, for the listeners registered so far. */
76
110
  callHook(event: string, ...args: unknown[]): unknown[];
77
111
  /** Read back a registered setting's current value. */
@@ -113,5 +147,5 @@ declare global {
113
147
  const CONST: any;
114
148
  }
115
149
  //#endregion
116
- export { MockDocument as a, withMockFoundry as i, RecordedHook as n, createMockActor as o, RecordedSetting as r, createMockItem as s, MockFoundry as t };
117
- //# sourceMappingURL=index-CbdI4_4T.d.mts.map
150
+ export { RecordedSheet as a, createMockActor as c, RecordedSetting as i, createMockItem as l, RecordedEnricher as n, withMockFoundry as o, RecordedHook as r, MockDocument as s, MockFoundry as t };
151
+ //# sourceMappingURL=index-CcUzNUCI.d.mts.map
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { n as QuenchContext, r as registerBatch, t as BatchOptions } from "./index-DLgaf8hZ.mjs";
2
- import { a as MockDocument, i as withMockFoundry, n as RecordedHook, o as createMockActor, r as RecordedSetting, s as createMockItem, t as MockFoundry } from "./index-CbdI4_4T.mjs";
3
- export { type BatchOptions, type MockDocument, type MockFoundry, type QuenchContext, type RecordedHook, type RecordedSetting, createMockActor, createMockItem, registerBatch, withMockFoundry };
2
+ import { a as RecordedSheet, c as createMockActor, i as RecordedSetting, l as createMockItem, n as RecordedEnricher, o as withMockFoundry, r as RecordedHook, s as MockDocument, t as MockFoundry } from "./index-CcUzNUCI.mjs";
3
+ export { type BatchOptions, type MockDocument, type MockFoundry, type QuenchContext, type RecordedEnricher, type RecordedHook, type RecordedSetting, type RecordedSheet, createMockActor, createMockItem, registerBatch, withMockFoundry };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { t as registerBatch } from "./quench-CdVnhmcA.mjs";
2
- import { n as createMockActor, r as createMockItem, t as withMockFoundry } from "./vitest-Bd89S6GJ.mjs";
2
+ import { n as createMockActor, r as createMockItem, t as withMockFoundry } from "./vitest-D0qlXJeT.mjs";
3
3
  export { createMockActor, createMockItem, registerBatch, withMockFoundry };
@@ -1,2 +1,2 @@
1
- import { a as MockDocument, i as withMockFoundry, n as RecordedHook, o as createMockActor, r as RecordedSetting, s as createMockItem, t as MockFoundry } from "../index-CbdI4_4T.mjs";
2
- export { type MockDocument, type MockFoundry, type RecordedHook, type RecordedSetting, createMockActor, createMockItem, withMockFoundry };
1
+ import { a as RecordedSheet, c as createMockActor, i as RecordedSetting, l as createMockItem, n as RecordedEnricher, o as withMockFoundry, r as RecordedHook, s as MockDocument, t as MockFoundry } from "../index-CcUzNUCI.mjs";
2
+ export { type MockDocument, type MockFoundry, type RecordedEnricher, type RecordedHook, type RecordedSetting, type RecordedSheet, createMockActor, createMockItem, withMockFoundry };
@@ -1,2 +1,2 @@
1
- import { n as createMockActor, r as createMockItem, t as withMockFoundry } from "../vitest-Bd89S6GJ.mjs";
1
+ import { n as createMockActor, r as createMockItem, t as withMockFoundry } from "../vitest-D0qlXJeT.mjs";
2
2
  export { createMockActor, createMockItem, withMockFoundry };
@@ -166,6 +166,8 @@ function withMockFoundry(options = {}) {
166
166
  name: "Gamemaster",
167
167
  ...options.user
168
168
  };
169
+ const sheets = [];
170
+ const enrichers = [];
169
171
  scope.Hooks = Hooks;
170
172
  scope.CONST = { DOCUMENT_OWNERSHIP_LEVELS: {
171
173
  NONE: 0,
@@ -182,7 +184,7 @@ function withMockFoundry(options = {}) {
182
184
  dataModels: {},
183
185
  sheetClasses: {}
184
186
  },
185
- TextEditor: { enrichers: [] },
187
+ TextEditor: { enrichers },
186
188
  Combat: {},
187
189
  ActiveEffect: {},
188
190
  statusEffects: [],
@@ -229,12 +231,31 @@ function withMockFoundry(options = {}) {
229
231
  ActorSheetV2: class {},
230
232
  ItemSheetV2: class {}
231
233
  },
234
+ apps: { DocumentSheetConfig: {
235
+ registerSheet(documentClass, scope, sheetClass, sheetOptions = {}) {
236
+ sheets.push({
237
+ key: `${scope}.${sheetClass.name}`,
238
+ scope,
239
+ id: sheetClass.name,
240
+ sheetClass,
241
+ documentClass,
242
+ options: sheetOptions
243
+ });
244
+ },
245
+ unregisterSheet: () => {}
246
+ } },
232
247
  ux: {},
233
248
  instances: /* @__PURE__ */ new Map()
234
249
  },
235
250
  documents: { collections: {
236
- Actors: { registerSheet: () => {} },
237
- Items: { registerSheet: () => {} }
251
+ Actors: {
252
+ registerSheet: () => {},
253
+ unregisterSheet: () => {}
254
+ },
255
+ Items: {
256
+ registerSheet: () => {},
257
+ unregisterSheet: () => {}
258
+ }
238
259
  } },
239
260
  ...options.foundry
240
261
  };
@@ -270,6 +291,8 @@ function withMockFoundry(options = {}) {
270
291
  hooks,
271
292
  settings,
272
293
  notifications,
294
+ sheets,
295
+ enrichers,
273
296
  callHook,
274
297
  getSetting: (namespace, key) => values.get(`${namespace}.${key}`),
275
298
  restore() {
@@ -281,4 +304,4 @@ function withMockFoundry(options = {}) {
281
304
  //#endregion
282
305
  export { createMockActor as n, createMockItem as r, withMockFoundry as t };
283
306
 
284
- //# sourceMappingURL=vitest-Bd89S6GJ.mjs.map
307
+ //# sourceMappingURL=vitest-D0qlXJeT.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest-D0qlXJeT.mjs","names":[],"sources":["../src/vitest/mock-foundry.ts","../src/vitest/with-mock-foundry.ts"],"sourcesContent":["/**\n * A Foundry runtime, faked well enough to import a package and construct\n * its classes.\n *\n * Every `@vttforge/core` test built this by hand before this existed, and no\n * two built it quite the same way. That is the problem: the globals a package\n * needs are not obvious, they are not documented anywhere, and you discover\n * them one `ReferenceError` at a time.\n *\n * What this covers is the boundary the SDK actually sits on — everything up\n * to the moment a window renders. `_renderHTML` and beyond needs a real\n * browser and a real Foundry; that is what the Quench half is for.\n */\n\n/** A Foundry document, as much of one as a unit test needs. */\nexport interface MockDocument {\n id: string;\n name: string;\n type: string;\n // biome-ignore lint/suspicious/noExplicitAny: a document's system data is whatever its schema says\n system: Record<string, any>;\n flags: Record<string, Record<string, unknown>>;\n getFlag(scope: string, key: string): unknown;\n setFlag(scope: string, key: string, value: unknown): Promise<MockDocument>;\n unsetFlag(scope: string, key: string): Promise<MockDocument>;\n update(delta: Record<string, unknown>): Promise<MockDocument>;\n /** Every update this document received, in order. */\n readonly updates: ReadonlyArray<Record<string, unknown>>;\n}\n\ninterface MockDocumentOptions {\n id?: string;\n name?: string;\n type?: string;\n // biome-ignore lint/suspicious/noExplicitAny: mirrors MockDocument.system\n system?: Record<string, any>;\n flags?: Record<string, Record<string, unknown>>;\n}\n\nlet nextId = 0;\n\n/** `{'system.hp.value': 3}` → `{system: {hp: {value: 3}}}`. */\nfunction expand(flat: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [path, value] of Object.entries(flat)) {\n const parts = path.split('.');\n const last = parts.pop();\n if (!last) continue;\n let target = out;\n for (const part of parts) {\n target[part] ??= {};\n target = target[part] as Record<string, unknown>;\n }\n target[last] = value;\n }\n return out;\n}\n\n/** Deep-merge, the way a document update behaves. Arrays replace wholesale. */\nfunction merge(target: Record<string, unknown>, source: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(source)) {\n const isPlain =\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype;\n if (isPlain) {\n if (typeof target[key] !== 'object' || target[key] === null) target[key] = {};\n merge(target[key] as Record<string, unknown>, value as Record<string, unknown>);\n } else {\n target[key] = value;\n }\n }\n}\n\nfunction createMockDocument(kind: string, options: MockDocumentOptions = {}): MockDocument {\n nextId += 1;\n const updates: Record<string, unknown>[] = [];\n\n const doc: MockDocument = {\n id: options.id ?? `${kind}${String(nextId).padStart(12, '0')}`,\n name: options.name ?? `Test ${kind}`,\n type: options.type ?? 'base',\n system: options.system ?? {},\n flags: options.flags ?? {},\n updates,\n\n getFlag: (scope, key) => doc.flags[scope]?.[key],\n\n setFlag: async (scope, key, value) => {\n doc.flags[scope] ??= {};\n doc.flags[scope][key] = value;\n return doc;\n },\n\n unsetFlag: async (scope, key) => {\n delete doc.flags[scope]?.[key];\n return doc;\n },\n\n /**\n * Records the delta and merges it in.\n *\n * Merges, not replaces — which is what Foundry does and what a mock has\n * to match. Assigning `{system: {hp: {value: 4}}}` wholesale drops\n * `hp.max`, and a test written against that passes while the real thing\n * loses data.\n *\n * Dotted keys are expanded first, because that is how updates arrive.\n */\n update: async (delta) => {\n updates.push(delta);\n merge(doc as unknown as Record<string, unknown>, expand(delta));\n return doc;\n },\n };\n\n return doc;\n}\n\n/** An Actor for a test. Its `updates` record what the code under test wrote. */\nexport function createMockActor(options: MockDocumentOptions = {}): MockDocument {\n return createMockDocument('Actor', { type: 'character', ...options });\n}\n\n/** An Item for a test. */\nexport function createMockItem(options: MockDocumentOptions = {}): MockDocument {\n return createMockDocument('Item', { type: 'base', ...options });\n}\n","/**\n * Install the Foundry globals a package needs, and take them away again.\n *\n * The globals are the awkward part of testing anything built on Foundry:\n * `foundry`, `game`, `CONFIG`, `Hooks`, `ui`, `CONST`. A package touches some\n * subset, the subset is not written down, and you find it one\n * `ReferenceError` at a time.\n *\n * Everything here is a plain object or a recording stub, so a test can assert\n * on what was registered rather than only on what did not throw.\n */\n\n/** A hook registration the code under test made. */\nexport interface RecordedHook {\n event: string;\n once: boolean;\n fn: (...args: unknown[]) => unknown;\n}\n\n/** A setting the code under test registered. */\nexport interface RecordedSetting {\n namespace: string;\n key: string;\n config: Record<string, unknown>;\n}\n\n/** One `DocumentSheetConfig.registerSheet`, as VTTForge makes it. */\nexport interface RecordedSheet {\n /** The key Foundry persists: `<package id>.<sheet id>`. */\n readonly key: string;\n /** The package that registered it. */\n readonly scope: string;\n /** The sheet id — the class name VTTForge pinned. */\n readonly id: string;\n readonly sheetClass: unknown;\n readonly documentClass: unknown;\n readonly options: Record<string, unknown>;\n}\n\n/** One entry pushed onto `CONFIG.TextEditor.enrichers`. */\nexport interface RecordedEnricher {\n /** The namespaced id: `<package id>.<enricher id>`. */\n readonly id: string;\n readonly pattern: RegExp;\n readonly onRender?: unknown;\n}\n\nexport interface MockFoundry {\n /** Every `Hooks.on` and `Hooks.once`, in order. */\n readonly hooks: ReadonlyArray<RecordedHook>;\n /** Every `game.settings.register`, in order. */\n readonly settings: ReadonlyArray<RecordedSetting>;\n /** Every notification raised, by severity. */\n readonly notifications: ReadonlyArray<{ level: 'info' | 'warn' | 'error'; message: string }>;\n /**\n * Every sheet registered, in order.\n *\n * `key` is the thing worth asserting: Foundry saves it on each document\n * using the sheet, so a test that pins the key is a test that the reader's\n * choice survives your next build.\n */\n readonly sheets: ReadonlyArray<RecordedSheet>;\n /**\n * Every text enricher registered, in order.\n *\n * `id` is namespaced, which is what stops a common name from colliding with\n * another package — and what makes `onRender` fire at all.\n */\n readonly enrichers: ReadonlyArray<RecordedEnricher>;\n /** Fire a hook the way Foundry would, for the listeners registered so far. */\n callHook(event: string, ...args: unknown[]): unknown[];\n /** Read back a registered setting's current value. */\n getSetting(namespace: string, key: string): unknown;\n /** Put every global back the way it was. */\n restore(): void;\n}\n\ninterface MockFoundryOptions {\n /** The current user. Defaults to a GM, since most module code checks. */\n user?: { id?: string; isGM?: boolean; name?: string };\n /** Extra `foundry.*` members, merged over the defaults. */\n foundry?: Record<string, unknown>;\n /** Extra `game.*` members, merged over the defaults. */\n game?: Record<string, unknown>;\n}\n\nconst GLOBALS = ['foundry', 'game', 'CONFIG', 'Hooks', 'ui', 'CONST'] as const;\n\n/**\n * Flatten and expand, the way `foundry.utils` does.\n *\n * Provided because SDK code calls them on the global, not because a test\n * needs them: leaving them out means every consumer stubs them again.\n */\nfunction flattenObject(obj: Record<string, unknown>, prefix = ''): Record<string, unknown> {\n const flat: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n const path = prefix ? `${prefix}.${key}` : key;\n const isPlain =\n value !== null &&\n typeof value === 'object' &&\n Object.getPrototypeOf(value) === Object.prototype;\n if (isPlain && Object.keys(value as object).length > 0) {\n Object.assign(flat, flattenObject(value as Record<string, unknown>, path));\n } else {\n flat[path] = value;\n }\n }\n return flat;\n}\n\nfunction expandObject(flat: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [path, value] of Object.entries(flat)) {\n const parts = path.split('.');\n const last = parts.pop();\n if (!last) continue;\n let target = out;\n for (const part of parts) {\n target[part] ??= {};\n target = target[part] as Record<string, unknown>;\n }\n target[last] = value;\n }\n return out;\n}\n\n/**\n * Stand up the globals, run the callback, tear them down.\n *\n * ```ts\n * const foundry = withMockFoundry();\n * registerMyModule();\n * expect(foundry.hooks.map((h) => h.event)).toContain('init');\n * foundry.restore();\n * ```\n */\nexport function withMockFoundry(options: MockFoundryOptions = {}): MockFoundry {\n const saved = new Map<string, unknown>();\n const scope = globalThis as Record<string, unknown>;\n for (const name of GLOBALS) saved.set(name, scope[name]);\n\n const hooks: RecordedHook[] = [];\n const settings: RecordedSetting[] = [];\n const values = new Map<string, unknown>();\n const notifications: { level: 'info' | 'warn' | 'error'; message: string }[] = [];\n\n const Hooks = {\n on: (event: string, fn: (...a: unknown[]) => unknown) => {\n hooks.push({ event, once: false, fn });\n return hooks.length;\n },\n once: (event: string, fn: (...a: unknown[]) => unknown) => {\n hooks.push({ event, once: true, fn });\n return hooks.length;\n },\n off: () => {},\n call: (event: string, ...args: unknown[]) => callHook(event, ...args).every((r) => r !== false),\n callAll: (event: string, ...args: unknown[]) => {\n callHook(event, ...args);\n return true;\n },\n };\n\n function callHook(event: string, ...args: unknown[]): unknown[] {\n return hooks.filter((h) => h.event === event).map((h) => h.fn(...args));\n }\n\n const user = { id: 'user000000000001', isGM: true, name: 'Gamemaster', ...options.user };\n\n const sheets: RecordedSheet[] = [];\n const enrichers: RecordedEnricher[] = [];\n\n scope.Hooks = Hooks;\n scope.CONST = { DOCUMENT_OWNERSHIP_LEVELS: { NONE: 0, LIMITED: 1, OBSERVER: 2, OWNER: 3 } };\n scope.CONFIG = {\n Actor: { dataModels: {}, sheetClasses: {} },\n Item: { dataModels: {}, sheetClasses: {} },\n TextEditor: { enrichers },\n Combat: {},\n ActiveEffect: {},\n statusEffects: [],\n debug: { hooks: false },\n };\n scope.ui = {\n notifications: {\n info: (m: string) => notifications.push({ level: 'info', message: m }),\n warn: (m: string) => notifications.push({ level: 'warn', message: m }),\n error: (m: string) => notifications.push({ level: 'error', message: m }),\n },\n };\n scope.foundry = {\n utils: {\n flattenObject,\n expandObject,\n mergeObject: (a: Record<string, unknown>, b: Record<string, unknown>) => ({ ...a, ...b }),\n deepClone: <T>(v: T): T => structuredClone(v),\n randomID: () => Math.random().toString(36).slice(2, 18),\n isNewerVersion: (a: string, b: string) =>\n a.localeCompare(b, undefined, { numeric: true }) > 0,\n },\n abstract: { TypeDataModel: class {}, DataModel: class {}, Document: class {} },\n data: { fields: {} },\n applications: {\n api: { ApplicationV2: class {}, HandlebarsApplicationMixin: (b: unknown) => b },\n sheets: { ActorSheetV2: class {}, ItemSheetV2: class {} },\n apps: {\n // Where `registerSystem({ sheets })` and `registerModule({ sheets })`\n // register. Foundry builds the key from the class name, so this\n // records the same key it would persist.\n DocumentSheetConfig: {\n registerSheet(\n documentClass: unknown,\n scope: string,\n sheetClass: { name: string },\n sheetOptions: Record<string, unknown> = {},\n ) {\n sheets.push({\n key: `${scope}.${sheetClass.name}`,\n scope,\n id: sheetClass.name,\n sheetClass,\n documentClass,\n options: sheetOptions,\n });\n },\n unregisterSheet: () => {},\n },\n },\n ux: {},\n instances: new Map(),\n },\n documents: {\n collections: {\n Actors: { registerSheet: () => {}, unregisterSheet: () => {} },\n Items: { registerSheet: () => {}, unregisterSheet: () => {} },\n },\n },\n ...options.foundry,\n };\n scope.game = {\n user,\n userId: user.id,\n ready: true,\n modules: new Map(),\n actors: [],\n items: [],\n i18n: {\n localize: (key: string) => key,\n format: (key: string, data: Record<string, unknown>) => `${key} ${JSON.stringify(data)}`,\n },\n settings: {\n register: (namespace: string, key: string, config: Record<string, unknown>) => {\n settings.push({ namespace, key, config });\n values.set(`${namespace}.${key}`, config.default);\n },\n get: (namespace: string, key: string) => values.get(`${namespace}.${key}`),\n set: async (namespace: string, key: string, value: unknown) => {\n values.set(`${namespace}.${key}`, value);\n return value;\n },\n },\n ...options.game,\n };\n\n return {\n hooks,\n settings,\n notifications,\n sheets,\n enrichers,\n callHook,\n getSetting: (namespace, key) => values.get(`${namespace}.${key}`),\n restore() {\n for (const [name, value] of saved) {\n if (value === undefined) delete scope[name];\n else scope[name] = value;\n }\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vttforge/testing",
3
- "version": "0.2.0",
4
- "description": "Quench-compatible test helpers and Foundry mocks for VTTForge systems and modules.",
3
+ "version": "0.3.1",
4
+ "description": "Foundry mocks for Vitest and Quench helpers, for testing VTTForge systems and modules.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/vttforge/vttforge#readme",
@@ -1 +0,0 @@
1
- {"version":3,"file":"vitest-Bd89S6GJ.mjs","names":[],"sources":["../src/vitest/mock-foundry.ts","../src/vitest/with-mock-foundry.ts"],"sourcesContent":["/**\n * A Foundry runtime, faked well enough to import a package and construct\n * its classes.\n *\n * Every `@vttforge/core` test built this by hand before this existed, and no\n * two built it quite the same way. That is the problem: the globals a package\n * needs are not obvious, they are not documented anywhere, and you discover\n * them one `ReferenceError` at a time.\n *\n * What this covers is the boundary the SDK actually sits on — everything up\n * to the moment a window renders. `_renderHTML` and beyond needs a real\n * browser and a real Foundry; that is what the Quench half is for.\n */\n\n/** A Foundry document, as much of one as a unit test needs. */\nexport interface MockDocument {\n id: string;\n name: string;\n type: string;\n // biome-ignore lint/suspicious/noExplicitAny: a document's system data is whatever its schema says\n system: Record<string, any>;\n flags: Record<string, Record<string, unknown>>;\n getFlag(scope: string, key: string): unknown;\n setFlag(scope: string, key: string, value: unknown): Promise<MockDocument>;\n unsetFlag(scope: string, key: string): Promise<MockDocument>;\n update(delta: Record<string, unknown>): Promise<MockDocument>;\n /** Every update this document received, in order. */\n readonly updates: ReadonlyArray<Record<string, unknown>>;\n}\n\ninterface MockDocumentOptions {\n id?: string;\n name?: string;\n type?: string;\n // biome-ignore lint/suspicious/noExplicitAny: mirrors MockDocument.system\n system?: Record<string, any>;\n flags?: Record<string, Record<string, unknown>>;\n}\n\nlet nextId = 0;\n\n/** `{'system.hp.value': 3}` → `{system: {hp: {value: 3}}}`. */\nfunction expand(flat: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [path, value] of Object.entries(flat)) {\n const parts = path.split('.');\n const last = parts.pop();\n if (!last) continue;\n let target = out;\n for (const part of parts) {\n target[part] ??= {};\n target = target[part] as Record<string, unknown>;\n }\n target[last] = value;\n }\n return out;\n}\n\n/** Deep-merge, the way a document update behaves. Arrays replace wholesale. */\nfunction merge(target: Record<string, unknown>, source: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(source)) {\n const isPlain =\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype;\n if (isPlain) {\n if (typeof target[key] !== 'object' || target[key] === null) target[key] = {};\n merge(target[key] as Record<string, unknown>, value as Record<string, unknown>);\n } else {\n target[key] = value;\n }\n }\n}\n\nfunction createMockDocument(kind: string, options: MockDocumentOptions = {}): MockDocument {\n nextId += 1;\n const updates: Record<string, unknown>[] = [];\n\n const doc: MockDocument = {\n id: options.id ?? `${kind}${String(nextId).padStart(12, '0')}`,\n name: options.name ?? `Test ${kind}`,\n type: options.type ?? 'base',\n system: options.system ?? {},\n flags: options.flags ?? {},\n updates,\n\n getFlag: (scope, key) => doc.flags[scope]?.[key],\n\n setFlag: async (scope, key, value) => {\n doc.flags[scope] ??= {};\n doc.flags[scope][key] = value;\n return doc;\n },\n\n unsetFlag: async (scope, key) => {\n delete doc.flags[scope]?.[key];\n return doc;\n },\n\n /**\n * Records the delta and merges it in.\n *\n * Merges, not replaces — which is what Foundry does and what a mock has\n * to match. Assigning `{system: {hp: {value: 4}}}` wholesale drops\n * `hp.max`, and a test written against that passes while the real thing\n * loses data.\n *\n * Dotted keys are expanded first, because that is how updates arrive.\n */\n update: async (delta) => {\n updates.push(delta);\n merge(doc as unknown as Record<string, unknown>, expand(delta));\n return doc;\n },\n };\n\n return doc;\n}\n\n/** An Actor for a test. Its `updates` record what the code under test wrote. */\nexport function createMockActor(options: MockDocumentOptions = {}): MockDocument {\n return createMockDocument('Actor', { type: 'character', ...options });\n}\n\n/** An Item for a test. */\nexport function createMockItem(options: MockDocumentOptions = {}): MockDocument {\n return createMockDocument('Item', { type: 'base', ...options });\n}\n","/**\n * Install the Foundry globals a package needs, and take them away again.\n *\n * The globals are the awkward part of testing anything built on Foundry:\n * `foundry`, `game`, `CONFIG`, `Hooks`, `ui`, `CONST`. A package touches some\n * subset, the subset is not written down, and you find it one\n * `ReferenceError` at a time.\n *\n * Everything here is a plain object or a recording stub, so a test can assert\n * on what was registered rather than only on what did not throw.\n */\n\n/** A hook registration the code under test made. */\nexport interface RecordedHook {\n event: string;\n once: boolean;\n fn: (...args: unknown[]) => unknown;\n}\n\n/** A setting the code under test registered. */\nexport interface RecordedSetting {\n namespace: string;\n key: string;\n config: Record<string, unknown>;\n}\n\nexport interface MockFoundry {\n /** Every `Hooks.on` and `Hooks.once`, in order. */\n readonly hooks: ReadonlyArray<RecordedHook>;\n /** Every `game.settings.register`, in order. */\n readonly settings: ReadonlyArray<RecordedSetting>;\n /** Every notification raised, by severity. */\n readonly notifications: ReadonlyArray<{ level: 'info' | 'warn' | 'error'; message: string }>;\n /** Fire a hook the way Foundry would, for the listeners registered so far. */\n callHook(event: string, ...args: unknown[]): unknown[];\n /** Read back a registered setting's current value. */\n getSetting(namespace: string, key: string): unknown;\n /** Put every global back the way it was. */\n restore(): void;\n}\n\ninterface MockFoundryOptions {\n /** The current user. Defaults to a GM, since most module code checks. */\n user?: { id?: string; isGM?: boolean; name?: string };\n /** Extra `foundry.*` members, merged over the defaults. */\n foundry?: Record<string, unknown>;\n /** Extra `game.*` members, merged over the defaults. */\n game?: Record<string, unknown>;\n}\n\nconst GLOBALS = ['foundry', 'game', 'CONFIG', 'Hooks', 'ui', 'CONST'] as const;\n\n/**\n * Flatten and expand, the way `foundry.utils` does.\n *\n * Provided because SDK code calls them on the global, not because a test\n * needs them: leaving them out means every consumer stubs them again.\n */\nfunction flattenObject(obj: Record<string, unknown>, prefix = ''): Record<string, unknown> {\n const flat: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n const path = prefix ? `${prefix}.${key}` : key;\n const isPlain =\n value !== null &&\n typeof value === 'object' &&\n Object.getPrototypeOf(value) === Object.prototype;\n if (isPlain && Object.keys(value as object).length > 0) {\n Object.assign(flat, flattenObject(value as Record<string, unknown>, path));\n } else {\n flat[path] = value;\n }\n }\n return flat;\n}\n\nfunction expandObject(flat: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [path, value] of Object.entries(flat)) {\n const parts = path.split('.');\n const last = parts.pop();\n if (!last) continue;\n let target = out;\n for (const part of parts) {\n target[part] ??= {};\n target = target[part] as Record<string, unknown>;\n }\n target[last] = value;\n }\n return out;\n}\n\n/**\n * Stand up the globals, run the callback, tear them down.\n *\n * ```ts\n * const foundry = withMockFoundry();\n * registerMyModule();\n * expect(foundry.hooks.map((h) => h.event)).toContain('init');\n * foundry.restore();\n * ```\n */\nexport function withMockFoundry(options: MockFoundryOptions = {}): MockFoundry {\n const saved = new Map<string, unknown>();\n const scope = globalThis as Record<string, unknown>;\n for (const name of GLOBALS) saved.set(name, scope[name]);\n\n const hooks: RecordedHook[] = [];\n const settings: RecordedSetting[] = [];\n const values = new Map<string, unknown>();\n const notifications: { level: 'info' | 'warn' | 'error'; message: string }[] = [];\n\n const Hooks = {\n on: (event: string, fn: (...a: unknown[]) => unknown) => {\n hooks.push({ event, once: false, fn });\n return hooks.length;\n },\n once: (event: string, fn: (...a: unknown[]) => unknown) => {\n hooks.push({ event, once: true, fn });\n return hooks.length;\n },\n off: () => {},\n call: (event: string, ...args: unknown[]) => callHook(event, ...args).every((r) => r !== false),\n callAll: (event: string, ...args: unknown[]) => {\n callHook(event, ...args);\n return true;\n },\n };\n\n function callHook(event: string, ...args: unknown[]): unknown[] {\n return hooks.filter((h) => h.event === event).map((h) => h.fn(...args));\n }\n\n const user = { id: 'user000000000001', isGM: true, name: 'Gamemaster', ...options.user };\n\n scope.Hooks = Hooks;\n scope.CONST = { DOCUMENT_OWNERSHIP_LEVELS: { NONE: 0, LIMITED: 1, OBSERVER: 2, OWNER: 3 } };\n scope.CONFIG = {\n Actor: { dataModels: {}, sheetClasses: {} },\n Item: { dataModels: {}, sheetClasses: {} },\n TextEditor: { enrichers: [] },\n Combat: {},\n ActiveEffect: {},\n statusEffects: [],\n debug: { hooks: false },\n };\n scope.ui = {\n notifications: {\n info: (m: string) => notifications.push({ level: 'info', message: m }),\n warn: (m: string) => notifications.push({ level: 'warn', message: m }),\n error: (m: string) => notifications.push({ level: 'error', message: m }),\n },\n };\n scope.foundry = {\n utils: {\n flattenObject,\n expandObject,\n mergeObject: (a: Record<string, unknown>, b: Record<string, unknown>) => ({ ...a, ...b }),\n deepClone: <T>(v: T): T => structuredClone(v),\n randomID: () => Math.random().toString(36).slice(2, 18),\n isNewerVersion: (a: string, b: string) =>\n a.localeCompare(b, undefined, { numeric: true }) > 0,\n },\n abstract: { TypeDataModel: class {}, DataModel: class {}, Document: class {} },\n data: { fields: {} },\n applications: {\n api: { ApplicationV2: class {}, HandlebarsApplicationMixin: (b: unknown) => b },\n sheets: { ActorSheetV2: class {}, ItemSheetV2: class {} },\n ux: {},\n instances: new Map(),\n },\n documents: {\n collections: { Actors: { registerSheet: () => {} }, Items: { registerSheet: () => {} } },\n },\n ...options.foundry,\n };\n scope.game = {\n user,\n userId: user.id,\n ready: true,\n modules: new Map(),\n actors: [],\n items: [],\n i18n: {\n localize: (key: string) => key,\n format: (key: string, data: Record<string, unknown>) => `${key} ${JSON.stringify(data)}`,\n },\n settings: {\n register: (namespace: string, key: string, config: Record<string, unknown>) => {\n settings.push({ namespace, key, config });\n values.set(`${namespace}.${key}`, config.default);\n },\n get: (namespace: string, key: string) => values.get(`${namespace}.${key}`),\n set: async (namespace: string, key: string, value: unknown) => {\n values.set(`${namespace}.${key}`, value);\n return value;\n },\n },\n ...options.game,\n };\n\n return {\n hooks,\n settings,\n notifications,\n callHook,\n getSetting: (namespace, key) => values.get(`${namespace}.${key}`),\n restore() {\n for (const [name, value] of saved) {\n if (value === undefined) delete scope[name];\n else scope[name] = value;\n }\n },\n };\n}\n"],"mappings":"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"}