@testforgejs/vue-test-preset-recommended 1.0.0-beta.1 → 1.0.0-beta.3

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/README.md CHANGED
@@ -2,9 +2,42 @@
2
2
 
3
3
  Recommended **Vitest** presets for the [TestForge](https://github.com/testforgejs/testforge) Vue 3 component testing framework.
4
4
 
5
- This package builds on [`@testforgejs/vue-test-preset-base`](https://www.npmjs.com/package/@testforgejs/vue-test-preset-base) and provides recommended defaults for projects using **Vitest**.
5
+ This package builds on [`@testforgejs/vue-test-preset-base`](https://www.npmjs.com/package/@testforgejs/vue-test-preset-base) and adds Vitest-specific configuration where required.
6
6
 
7
- The main difference from the base presets is the Pinia configuration: the recommended presets configure Pinia to use Vitest's `vi.fn` as its `createSpy` implementation.
7
+ The primary difference from the base presets is the Pinia configuration:
8
+
9
+ ```typescript
10
+ piniaPlugin.getDefaultOptions(vi);
11
+ ```
12
+
13
+ This allows the Pinia plugin to use Vitest's `vi.fn` as the `createSpy` implementation required by `@pinia/testing` when an explicit runner is provided.
14
+
15
+ Vue I18n and Vue Router do not require Vitest-specific configuration, so their presets are inherited directly from the base package.
16
+
17
+ Conceptually:
18
+
19
+ ```text
20
+ plugin
21
+ → owns reusable integration knowledge
22
+
23
+ base preset
24
+ → consumes runner-independent plugin defaults
25
+ → composes plugins without choosing a test runner
26
+
27
+ recommended preset
28
+ → consumes runner-aware plugin defaults
29
+ → adds Vitest-specific runner integration
30
+
31
+ project-owned preset
32
+ → materializes concrete plugin configuration
33
+ → owns application-specific policy
34
+ ```
35
+
36
+ The distinction between the recommended preset and a project-owned preset is intentional.
37
+
38
+ The TestForge-maintained recommended preset may use `getDefaultOptions()` because it deliberately follows the integration baseline exported by the installed plugin version.
39
+
40
+ A project-owned preset should normally materialize the concrete configuration it wants to preserve in its own source code.
8
41
 
9
42
  ## Installation
10
43
 
@@ -32,7 +65,7 @@ yarn add -D @testforgejs/vue-test-preset-recommended@beta
32
65
 
33
66
  ## Quick Usage
34
67
 
35
- Import the `presets` object and pass it to `createTestFramework()`:
68
+ For a single runtime environment, pass the recommended default preset through `preset`:
36
69
 
37
70
  ```typescript
38
71
  // tests/setup.ts
@@ -41,16 +74,17 @@ import { createTestFramework } from "@testforgejs/vue-test-core";
41
74
  import { presets } from "@testforgejs/vue-test-preset-recommended";
42
75
 
43
76
  const { testComponentFactory } = createTestFramework({
44
- presets,
77
+ preset: presets.default,
45
78
  });
46
79
 
47
80
  export { testComponentFactory };
48
81
  ```
49
82
 
50
- You can then use `testComponentFactory` in your component tests:
83
+ You can then use `testComponentFactory` in component tests:
51
84
 
52
85
  ```typescript
53
86
  import { describe, expect, it } from "vitest";
87
+
54
88
  import { testComponentFactory } from "./setup";
55
89
  import MyComponent from "./MyComponent.vue";
56
90
 
@@ -64,6 +98,50 @@ describe("MyComponent", () => {
64
98
  });
65
99
  ```
66
100
 
101
+ ## Single vs. Multiple Presets
102
+
103
+ `createTestFramework()` supports two mutually exclusive preset configuration forms.
104
+
105
+ Use `preset` when the framework needs a single runtime environment:
106
+
107
+ ```typescript
108
+ const { testComponentFactory } = createTestFramework({
109
+ preset: presets.default,
110
+ });
111
+ ```
112
+
113
+ A single preset is treated as the framework's default runtime environment.
114
+
115
+ Use `presets` when the framework should expose multiple named runtime environments:
116
+
117
+ ```typescript
118
+ const { testComponentFactory } = createTestFramework({
119
+ presets,
120
+ });
121
+ ```
122
+
123
+ The exported registry contains:
124
+
125
+ ```text
126
+ default
127
+ piniaPreset
128
+ i18nPreset
129
+ routerPreset
130
+ ```
131
+
132
+ Named presets registered through `presets` can be selected for individual component factory invocations through `extraOptions.preset`.
133
+
134
+ In general:
135
+
136
+ ```text
137
+ preset
138
+ → one runtime environment
139
+
140
+ presets
141
+ → multiple named runtime environments
142
+ → runtime selection through extraOptions.preset
143
+ ```
144
+
67
145
  ## Available Presets
68
146
 
69
147
  The package exports four presets:
@@ -73,136 +151,271 @@ The package exports four presets:
73
151
  - `presets.i18nPreset`
74
152
  - `presets.routerPreset`
75
153
 
76
- The presets are based on the corresponding presets from `@testforgejs/vue-test-preset-base`.
154
+ They are built from the corresponding presets exported by `@testforgejs/vue-test-preset-base`.
155
+
156
+ The current composition is equivalent to:
157
+
158
+ ```typescript
159
+ import { vi } from "vitest";
160
+
161
+ import { extendPreset } from "@testforgejs/vue-test-core";
162
+ import { presets as basePresets } from "@testforgejs/vue-test-preset-base";
163
+ import { piniaPlugin, PLUGIN_NAME as PINIA_PLUGIN_NAME } from "@testforgejs/vue-test-plugin-pinia";
164
+
165
+ export const presets = {
166
+ default: extendPreset(basePresets.default, {
167
+ defaults: {
168
+ [PINIA_PLUGIN_NAME]: piniaPlugin.getDefaultOptions(vi),
169
+ },
170
+ }),
171
+
172
+ piniaPreset: extendPreset(basePresets.piniaPreset, {
173
+ defaults: {
174
+ [PINIA_PLUGIN_NAME]: piniaPlugin.getDefaultOptions(vi),
175
+ },
176
+ }),
177
+
178
+ i18nPreset: basePresets.i18nPreset,
179
+ routerPreset: basePresets.routerPreset,
180
+ };
181
+ ```
182
+
183
+ Preset definitions use the standardized `PLUGIN_NAME` export as the configuration key instead of repeating plugin name literals.
184
+
185
+ This keeps the identifier exported by the plugin package as the single source of truth.
186
+
187
+ Because this package is a TestForge-maintained reusable preset, it intentionally obtains its runner-aware Pinia baseline through `getDefaultOptions(vi)`.
188
+
189
+ When only one of these environments is needed, use it directly through `createTestFramework({ preset })`.
77
190
 
78
191
  ### `presets.default`
79
192
 
80
- The default recommended preset enables:
193
+ The default recommended preset contains:
194
+
195
+ - Pinia — enabled;
196
+ - Vue I18n — enabled;
197
+ - Vue Router — declared but disabled.
198
+
199
+ It inherits the plugin composition from `presets.default` in the base package.
81
200
 
82
- - Pinia;
83
- - Vue I18n;
84
- - Vue Router — disabled by default.
201
+ The Pinia defaults are replaced with the runner-aware defaults provided by the Pinia plugin:
85
202
 
86
- It also provides default option factories for these managed plugins.
203
+ ```typescript
204
+ piniaPlugin.getDefaultOptions(vi);
205
+ ```
87
206
 
88
- For Pinia, the recommended preset additionally configures Vitest's `vi.fn` as the `createSpy` implementation.
207
+ The resulting Pinia options are currently equivalent to:
208
+
209
+ ```typescript
210
+ {
211
+ createSpy: vi.fn,
212
+ }
213
+ ```
214
+
215
+ TestForge does not override other `@pinia/testing` behavior.
216
+
217
+ In particular, actions follow the upstream `createTestingPinia()` behavior and are stubbed by default unless a project explicitly configures:
218
+
219
+ ```typescript
220
+ stubActions: false;
221
+ ```
222
+
223
+ Vue I18n and Vue Router continue to use the defaults selected by the base preset.
89
224
 
90
225
  ```typescript
91
226
  const { testComponentFactory } = createTestFramework({
92
- presets: {
93
- default: presets.default,
94
- },
227
+ preset: presets.default,
95
228
  });
96
229
  ```
97
230
 
98
- This is the preset intended for most Vitest-based TestForge projects.
231
+ This is the general-purpose preset intended for Vitest-based TestForge projects.
99
232
 
100
233
  ### `presets.piniaPreset`
101
234
 
102
- A minimal preset for tests that require Pinia.
103
-
104
- It enables Pinia and configures its spy factory to use Vitest:
235
+ A minimal preset containing only Pinia.
105
236
 
106
237
  ```typescript
107
238
  const { testComponentFactory } = createTestFramework({
108
- presets: {
109
- default: presets.piniaPreset,
110
- },
239
+ preset: presets.piniaPreset,
111
240
  });
112
241
  ```
113
242
 
114
- The Pinia option factory is based on the base preset and adds Vitest's `vi.fn`:
243
+ The preset is based on `basePresets.piniaPreset`, but replaces its runner-neutral Pinia defaults with:
115
244
 
116
245
  ```typescript
117
- pinia: () => ({
118
- ...basePiniaOptions,
119
- createSpy: vi.fn,
120
- });
246
+ piniaPlugin.getDefaultOptions(vi);
121
247
  ```
122
248
 
123
- This makes the preset immediately usable with `@pinia/testing` under Vitest.
249
+ This provides the Vitest spy implementation explicitly rather than relying on test-runner globals.
124
250
 
125
- ### `presets.i18nPreset`
251
+ The current Pinia defaults are equivalent to:
126
252
 
127
- A minimal preset for tests that require Vue I18n.
253
+ ```typescript
254
+ {
255
+ createSpy: vi.fn,
256
+ }
257
+ ```
128
258
 
129
- It enables Vue I18n using the base preset's default configuration:
259
+ Other Pinia testing behavior remains controlled by `@pinia/testing` unless the project overrides it.
130
260
 
131
- - Composition API mode (`legacy: false`);
132
- - English locale;
133
- - English fallback locale;
134
- - empty messages;
135
- - disabled missing-translation warnings.
261
+ ### `presets.i18nPreset`
136
262
 
137
- The plugin defaults are provided through a plugin options factory, so each pipeline context receives fresh plugin options.
263
+ A minimal preset containing only Vue I18n.
138
264
 
139
265
  ```typescript
140
266
  const { testComponentFactory } = createTestFramework({
141
- presets: {
142
- default: presets.i18nPreset,
143
- },
267
+ preset: presets.i18nPreset,
144
268
  });
145
269
  ```
146
270
 
147
- ### `presets.routerPreset`
271
+ This preset is inherited directly from `@testforgejs/vue-test-preset-base` because Vue I18n does not require Vitest-specific configuration.
272
+
273
+ The plugin defaults selected by the base preset are currently equivalent to:
274
+
275
+ ```typescript
276
+ {
277
+ legacy: false,
278
+ globalInjection: true,
279
+ }
280
+ ```
281
+
282
+ These options provide a project-independent Vue 3 integration baseline.
283
+
284
+ The preset intentionally does not choose:
148
285
 
149
- A minimal preset for tests that require Vue Router.
286
+ - an application locale;
287
+ - a fallback locale;
288
+ - translation messages;
289
+ - formatting configuration;
290
+ - missing-translation or fallback warning policy.
150
291
 
151
- It enables the managed Vue Router integration using the base preset's default router configuration.
292
+ Those settings belong in project-specific configuration.
293
+
294
+ ### `presets.routerPreset`
295
+
296
+ A minimal preset containing only Vue Router.
152
297
 
153
298
  ```typescript
154
299
  const { testComponentFactory } = createTestFramework({
155
- presets: {
156
- default: presets.routerPreset,
157
- },
300
+ preset: presets.routerPreset,
158
301
  });
159
302
  ```
160
303
 
161
- The router preset provides a test-oriented router configuration suitable for isolated component tests.
304
+ This preset is inherited directly from `@testforgejs/vue-test-preset-base`.
162
305
 
163
- Its plugin defaults are provided through a plugin options factory, which produces fresh options for each pipeline context.
306
+ The Router defaults selected by the base preset are currently equivalent to:
164
307
 
165
- ## Recommended vs Base Presets
308
+ ```typescript
309
+ {
310
+ history: createMemoryHistory(),
311
+ routes: [],
312
+ }
313
+ ```
314
+
315
+ The in-memory history keeps routing isolated from browser URL state.
166
316
 
167
- `@testforgejs/vue-test-preset-base` provides framework-neutral preset definitions.
317
+ The empty route table ensures that TestForge does not invent application-specific routes.
168
318
 
169
- `@testforgejs/vue-test-preset-recommended` builds on those presets and adds Vitest-specific configuration where necessary.
319
+ Projects that need actual routes or another history implementation should provide them explicitly in a project-owned preset.
320
+
321
+ ## Recommended vs Base Presets
170
322
 
171
- In particular:
323
+ `@testforgejs/vue-test-preset-base` provides runner-independent preset definitions.
324
+
325
+ `@testforgejs/vue-test-preset-recommended` adds Vitest-specific configuration only where it is required.
172
326
 
173
327
  ```text
174
328
  @testforgejs/vue-test-preset-base
175
- │
176
- ├── default
177
- ├── piniaPreset
178
- ├── i18nPreset
179
- └── routerPreset
180
- │
181
- ▼
329
+ │
330
+ ├── default
331
+ │ └── Pinia defaults without a runner
332
+ │
333
+ ├── piniaPreset
334
+ │ └── Pinia defaults without a runner
335
+ │
336
+ ├── i18nPreset
337
+ │
338
+ └── routerPreset
339
+ │
340
+ ▼
182
341
  @testforgejs/vue-test-preset-recommended
183
- │
184
- ├── default → adds Vitest vi.fn to Pinia
185
- ├── piniaPreset → adds Vitest vi.fn to Pinia
186
- ├── i18nPreset → inherited from base
187
- └── routerPreset → inherited from base
342
+ │
343
+ ├── default
344
+ │ └── piniaPlugin.getDefaultOptions(vi)
345
+ │
346
+ ├── piniaPreset
347
+ │ └── piniaPlugin.getDefaultOptions(vi)
348
+ │
349
+ ├── i18nPreset
350
+ │ └── inherited unchanged
351
+ │
352
+ └── routerPreset
353
+ └── inherited unchanged
188
354
  ```
189
355
 
190
- This separation keeps the base preset independent from a particular test runner while allowing the recommended preset to provide runner-specific defaults.
356
+ This separation keeps runner knowledge out of the base preset.
357
+
358
+ It also keeps knowledge about how Vitest integrates with Pinia inside the Pinia plugin rather than duplicating that configuration in the TestForge-maintained preset package.
191
359
 
192
- The Vitest-specific Pinia defaults are implemented as a **plugin options factory**. The factory invokes the base Pinia options factory and adds `createSpy`:
360
+ In other words:
361
+
362
+ ```text
363
+ recommended preset
364
+ → knows that the runner is Vitest
365
+
366
+ Pinia plugin
367
+ → knows how Vitest runner support maps to Pinia configuration
368
+ ```
369
+
370
+ ## Plugin Defaults Are Explicit
371
+
372
+ Plugin defaults are not automatically applied simply because a plugin appears in a preset manifest.
373
+
374
+ For example:
193
375
 
194
376
  ```typescript
195
- const defaultVitestPinia = (() => ({
196
- ...defaultPinia(),
197
- createSpy: vi.fn,
198
- })) satisfies PluginOptionsFactory<VueTestPiniaOptions>;
377
+ manifest: [
378
+ {
379
+ module: piniaPlugin,
380
+ enabled: true,
381
+ },
382
+ ];
383
+ ```
384
+
385
+ does not implicitly call:
386
+
387
+ ```typescript
388
+ piniaPlugin.getDefaultOptions(vi);
199
389
  ```
200
390
 
201
- The resulting factory can be reused by both the `default` and `piniaPreset` profiles.
391
+ The recommended preset explicitly selects the runner-aware Pinia defaults using the identifier exported by the plugin package:
392
+
393
+ ```typescript
394
+ defaults: {
395
+ [PINIA_PLUGIN_NAME]: piniaPlugin.getDefaultOptions(vi),
396
+ }
397
+ ```
398
+
399
+ The `PLUGIN_NAME` export identifies the plugin consistently across its module and preset configuration, while `getDefaultOptions()` provides the configuration factory selected by this TestForge-maintained preset.
400
+
401
+ Because the recommended preset deliberately references `getDefaultOptions(vi)`, it intentionally follows the runner-aware integration baseline provided by the installed Pinia plugin version.
402
+
403
+ Project-owned presets that need their concrete configuration to remain visible and stable across plugin baseline changes should materialize that configuration explicitly instead.
202
404
 
203
405
  ## Switching Presets at Runtime
204
406
 
205
- A **component factory invocation** can select a different registered preset using the fourth `extraOptions` argument:
407
+ Use the preset registry when the same TestForge framework instance should expose multiple named runtime environments:
408
+
409
+ ```typescript
410
+ import { createTestFramework } from "@testforgejs/vue-test-core";
411
+ import { presets } from "@testforgejs/vue-test-preset-recommended";
412
+
413
+ const { testComponentFactory } = createTestFramework({
414
+ presets,
415
+ });
416
+ ```
417
+
418
+ A component factory invocation can then select another registered preset using the fourth `extraOptions` argument:
206
419
 
207
420
  ```typescript
208
421
  const factory = testComponentFactory(MyComponent);
@@ -217,9 +430,7 @@ factory(
217
430
  );
218
431
  ```
219
432
 
220
- The selected preset defines the complete managed plugin runtime for that component factory invocation.
221
-
222
- This allows the same TestForge framework instance to use different runtime environments without creating a separate framework instance.
433
+ The selected preset defines the complete managed-plugin runtime for that invocation.
223
434
 
224
435
  For example:
225
436
 
@@ -234,64 +445,380 @@ factory(
234
445
  );
235
446
  ```
236
447
 
237
- selects the Pinia-only runtime environment for that invocation.
448
+ selects the Pinia-only runtime environment.
449
+
450
+ This allows a single TestForge framework instance to expose several predefined runtime profiles.
451
+
452
+ The similarly named options operate at different levels:
453
+
454
+ ```text
455
+ createTestFramework({ preset })
456
+ → configures one framework-wide runtime environment
457
+
458
+ createTestFramework({ presets })
459
+ → registers multiple named runtime environments
460
+
461
+ extraOptions.preset
462
+ → selects one registered runtime environment for a factory invocation
463
+ ```
464
+
465
+ Runtime selection through `extraOptions.preset` applies to named presets registered through `presets`.
238
466
 
239
467
  ## Customizing Presets
240
468
 
241
- The recommended presets are ordinary TestForge preset definitions and can be composed with [`extendPreset()`](https://github.com/testforgejs/testforge).
469
+ The recommended presets are ordinary TestForge preset definitions and can be composed with `extendPreset()`.
470
+
471
+ Application-specific behavior should normally be added in a project-owned preset rather than in TestForge plugin defaults.
472
+
473
+ When a project replaces a plugin factory, it should normally materialize the concrete configuration it wants to own rather than call `getDefaultOptions()` again.
474
+
475
+ This keeps the project's effective test environment visible in source and prevents future changes to a plugin baseline from silently changing that materialized configuration.
476
+
477
+ ### Customizing Pinia
242
478
 
243
- For example, a project can create its own preset from the recommended default:
479
+ For example, a project may want real Pinia actions to execute instead of using the upstream testing default that stubs them.
480
+
481
+ A project-owned preset can materialize both the required Vitest integration and the project-specific action policy:
244
482
 
245
483
  ```typescript
484
+ import { vi } from "vitest";
485
+
246
486
  import { extendPreset } from "@testforgejs/vue-test-core";
487
+ import { PLUGIN_NAME as PINIA_PLUGIN_NAME } from "@testforgejs/vue-test-plugin-pinia";
488
+ import { presets as recommendedPresets } from "@testforgejs/vue-test-preset-recommended";
489
+
490
+ const projectPreset = extendPreset(recommendedPresets.default, {
491
+ defaults: {
492
+ [PINIA_PLUGIN_NAME]: () => ({
493
+ createSpy: vi.fn,
494
+ stubActions: false,
495
+ }),
496
+ },
497
+ });
498
+ ```
499
+
500
+ This produces:
501
+
502
+ ```typescript
503
+ {
504
+ createSpy: vi.fn,
505
+ stubActions: false,
506
+ }
507
+ ```
508
+
509
+ The recommended preset obtains `createSpy: vi.fn` through the Pinia plugin's runner-aware defaults.
510
+
511
+ The project-owned preset materializes that value in its own source together with its `stubActions: false` policy.
512
+
513
+ Conceptually:
514
+
515
+ ```text
516
+ recommended preset
517
+ → follows runner-aware plugin baseline
518
+
519
+ project-owned preset
520
+ → materializes createSpy: vi.fn
521
+ → adds stubActions: false
522
+ → owns the resulting configuration
523
+ ```
247
524
 
525
+ A project using this single runtime environment can pass it directly to TestForge:
526
+
527
+ ```typescript
528
+ const { testComponentFactory } = createTestFramework({
529
+ preset: projectPreset,
530
+ });
531
+ ```
532
+
533
+ ### Customizing Vue I18n
534
+
535
+ Application locale and translation messages should be configured explicitly.
536
+
537
+ A project-owned preset should also materialize the integration values it intends to preserve:
538
+
539
+ ```typescript
540
+ import { extendPreset } from "@testforgejs/vue-test-core";
541
+ import { PLUGIN_NAME as I18N_PLUGIN_NAME } from "@testforgejs/vue-test-plugin-i18n";
248
542
  import { presets as recommendedPresets } from "@testforgejs/vue-test-preset-recommended";
249
543
 
250
- const projectPresets = {
251
- default: extendPreset(recommendedPresets.default, {
252
- defaults: {
253
- pinia: () => ({
254
- ...recommendedPresets.default.defaults.pinia(),
255
- // project-specific options
256
- }),
257
- },
258
- }),
259
- };
544
+ const projectPreset = extendPreset(recommendedPresets.default, {
545
+ defaults: {
546
+ [I18N_PLUGIN_NAME]: () => ({
547
+ legacy: false,
548
+ globalInjection: true,
549
+
550
+ locale: "en",
551
+ messages: {
552
+ en: {
553
+ hello: "Hello",
554
+ },
555
+ },
556
+ }),
557
+ },
558
+ });
260
559
  ```
261
560
 
262
- Because `defaults.pinia` is a plugin options factory, the base Pinia options are obtained by invoking the factory.
561
+ Here:
263
562
 
264
- When extending a preset, explicitly preserve any base plugin options that should remain enabled. Managed plugin configuration is intentionally replaced when an extension provides configuration for that plugin.
563
+ ```text
564
+ legacy / globalInjection
565
+ → materialized integration baseline
265
566
 
266
- For details about preset composition and configuration layering, see the [TestForge core documentation](https://github.com/testforgejs/testforge/blob/main/packages/vue-test-core/README.md).
567
+ locale / messages
568
+ → project-specific policy
569
+ ```
267
570
 
268
- ## Using a Preset with Other Framework Configuration
571
+ All of these values are now visible and owned by the project preset.
269
572
 
270
- The exported presets can be combined with other TestForge configuration options:
573
+ Use it directly when no runtime preset switching is needed:
271
574
 
272
575
  ```typescript
273
576
  const { testComponentFactory } = createTestFramework({
274
- presets,
275
- shallowByDefault: true,
577
+ preset: projectPreset,
578
+ });
579
+ ```
580
+
581
+ ### Customizing Vue Router
582
+
583
+ Application routes should also be configured explicitly.
584
+
585
+ The recommended default preset declares Router but keeps it disabled, so a project that wants Router as part of its default runtime should explicitly enable it and materialize its Router configuration:
586
+
587
+ ```typescript
588
+ import { createMemoryHistory } from "vue-router";
589
+ import { extendPreset } from "@testforgejs/vue-test-core";
590
+ import {
591
+ routerPlugin,
592
+ PLUGIN_NAME as ROUTER_PLUGIN_NAME,
593
+ } from "@testforgejs/vue-test-plugin-router";
594
+ import { presets as recommendedPresets } from "@testforgejs/vue-test-preset-recommended";
595
+
596
+ const projectPreset = extendPreset(recommendedPresets.default, {
597
+ manifest: [
598
+ {
599
+ module: routerPlugin,
600
+ enabled: true,
601
+ },
602
+ ],
603
+
604
+ defaults: {
605
+ [ROUTER_PLUGIN_NAME]: () => ({
606
+ history: createMemoryHistory(),
607
+
608
+ routes: [
609
+ {
610
+ path: "/",
611
+ component: HomeView,
612
+ },
613
+ ],
614
+ }),
615
+ },
616
+ });
617
+ ```
618
+
619
+ The project now owns both decisions:
620
+
621
+ ```text
622
+ manifest
623
+ → Router is enabled
624
+
625
+ defaults
626
+ → createMemoryHistory()
627
+ → application routes
628
+ ```
629
+
630
+ The in-memory history is explicit project-owned configuration rather than an indirect dependency on `routerPlugin.getDefaultOptions()`.
631
+
632
+ If the application requires another history implementation, replace it directly:
633
+
634
+ ```typescript
635
+ import { createWebHistory } from "vue-router";
636
+
637
+ const projectPreset = extendPreset(recommendedPresets.default, {
638
+ manifest: [
639
+ {
640
+ module: routerPlugin,
641
+ enabled: true,
642
+ },
643
+ ],
644
+
645
+ defaults: {
646
+ [ROUTER_PLUGIN_NAME]: () => ({
647
+ history: createWebHistory(),
648
+ routes,
649
+ }),
650
+ },
276
651
  });
277
652
  ```
278
653
 
279
- The preset provides the runtime plugin baseline, while individual component factory invocations and tests can override configuration when needed.
654
+ ## Replacing Preset Defaults
655
+
656
+ When an extension provides a plugin options factory, that factory replaces the corresponding factory from the source preset.
657
+
658
+ For example:
659
+
660
+ ```typescript
661
+ extendPreset(recommendedPresets.default, {
662
+ defaults: {
663
+ [PINIA_PLUGIN_NAME]: () => ({
664
+ createSpy: vi.fn,
665
+ stubActions: false,
666
+ }),
667
+ },
668
+ });
669
+ ```
670
+
671
+ TestForge does not implicitly merge this factory with:
672
+
673
+ ```typescript
674
+ piniaPlugin.getDefaultOptions(vi);
675
+ ```
676
+
677
+ or with the factory already present in `recommendedPresets.default`.
678
+
679
+ For a project-owned preset, explicit replacement is normally preferable because the effective configuration is visible in project source:
680
+
681
+ ```typescript
682
+ [PINIA_PLUGIN_NAME]: () => ({
683
+ createSpy: vi.fn,
684
+ stubActions: false,
685
+ })
686
+ ```
687
+
688
+ A project can instead deliberately preserve and spread the source preset factory:
689
+
690
+ ```typescript
691
+ const sourcePinia = recommendedPresets.default.defaults.pinia;
692
+
693
+ const projectPreset = extendPreset(recommendedPresets.default, {
694
+ defaults: {
695
+ [PINIA_PLUGIN_NAME]: () => ({
696
+ ...sourcePinia(),
697
+ stubActions: false,
698
+ }),
699
+ },
700
+ });
701
+ ```
702
+
703
+ This has different semantics.
704
+
705
+ The project now intentionally continues to depend on the Pinia factory provided by `recommendedPresets.default`.
706
+
707
+ If that source factory changes after a dependency update, the effective project configuration can change as well.
708
+
709
+ Conceptually:
710
+
711
+ ```text
712
+ spread source preset factory
713
+ → continue following source preset behavior
714
+
715
+ explicit replacement factory
716
+ → project owns the resulting configuration
717
+ ```
718
+
719
+ For a stable project-owned preset, prefer explicit materialization:
720
+
721
+ ```typescript
722
+ const projectPreset = extendPreset(recommendedPresets.default, {
723
+ defaults: {
724
+ [PINIA_PLUGIN_NAME]: () => ({
725
+ createSpy: vi.fn,
726
+ stubActions: false,
727
+ }),
728
+ },
729
+ });
730
+ ```
280
731
 
281
732
  ## Plugin Options Factory Isolation
282
733
 
283
- The `defaults` values in the presets are **plugin options factories**, not shared plugin configuration objects.
734
+ Preset `defaults` values are plugin options factories rather than shared configuration objects.
735
+
736
+ The recommended preset stores its Pinia options factory under the exported plugin identifier:
737
+
738
+ ```typescript
739
+ defaults: {
740
+ [PINIA_PLUGIN_NAME]: piniaPlugin.getDefaultOptions(vi),
741
+ }
742
+ ```
743
+
744
+ Because `PINIA_PLUGIN_NAME` resolves to `"pinia"`, the resolved preset can still be consumed through its normal property key.
745
+
746
+ Each invocation produces a fresh options object for the current pipeline context:
747
+
748
+ ```typescript
749
+ const first = presets.default.defaults.pinia();
750
+ const second = presets.default.defaults.pinia();
284
751
 
285
- When a plugin options factory is invoked, it produces fresh plugin options for the current pipeline context.
752
+ first !== second; // true
753
+ ```
754
+
755
+ Runner-independent presets preserve the same factory-based isolation.
756
+
757
+ For Router, the options factory also creates a fresh in-memory history instance for each invocation:
758
+
759
+ ```typescript
760
+ const first = presets.routerPreset.defaults.router();
761
+ const second = presets.routerPreset.defaults.router();
762
+
763
+ first.history !== second.history; // true
764
+ ```
765
+
766
+ This avoids unintentionally sharing mutable plugin configuration or runtime state between independent pipeline contexts.
767
+
768
+ A plugin options factory and a component factory operate at different levels:
769
+
770
+ ```text
771
+ plugin options factory
772
+ → creates plugin configuration
773
+
774
+ testComponentFactory()
775
+ → creates a reusable component mounting factory
776
+
777
+ component factory invocation
778
+ → creates an individual component test runtime
779
+ ```
780
+
781
+ The `getDefaultOptions(vi)` example in this section describes the implementation of the TestForge-maintained recommended preset.
782
+
783
+ A project-owned preset can preserve the same factory isolation while materializing its configuration explicitly:
784
+
785
+ ```typescript
786
+ defaults: {
787
+ [PINIA_PLUGIN_NAME]: () => ({
788
+ createSpy: vi.fn,
789
+ }),
790
+ }
791
+ ```
792
+
793
+ ## Using a Preset with Other Framework Configuration
794
+
795
+ When using a single runtime environment, combine it with other framework options through `preset`:
796
+
797
+ ```typescript
798
+ const { testComponentFactory } = createTestFramework({
799
+ preset: presets.default,
800
+ shallowByDefault: true,
801
+ });
802
+ ```
803
+
804
+ When multiple named environments are required, use the registry form instead:
805
+
806
+ ```typescript
807
+ const { testComponentFactory } = createTestFramework({
808
+ presets,
809
+ shallowByDefault: true,
810
+ });
811
+ ```
286
812
 
287
- This keeps mutable configuration isolated between independent pipeline contexts.
813
+ The selected preset provides the managed-plugin runtime baseline.
288
814
 
289
- This is separate from component factory isolation: `testComponentFactory()` creates a reusable function for mounting a component, while each invocation of that component factory establishes its own runtime environment.
815
+ A project-owned preset can materialize and replace that configuration explicitly, while individual component tests can apply local overrides where needed.
290
816
 
291
817
  ## Related Packages
292
818
 
293
819
  - [`@testforgejs/vue-test-core`](https://www.npmjs.com/package/@testforgejs/vue-test-core) — TestForge core framework
294
- - [`@testforgejs/vue-test-preset-base`](https://www.npmjs.com/package/@testforgejs/vue-test-preset-base) — base, runner-neutral presets
820
+ - [`@testforgejs/vue-test-preset-base`](https://www.npmjs.com/package/@testforgejs/vue-test-preset-base) — base runner-independent presets
821
+ - [`@testforgejs/vue-test-preset-recommended-jest`](https://www.npmjs.com/package/@testforgejs/vue-test-preset-recommended-jest) — Jest-specific recommended presets
295
822
  - [`@testforgejs/vue-test-plugin-pinia`](https://www.npmjs.com/package/@testforgejs/vue-test-plugin-pinia) — Pinia integration
296
823
  - [`@testforgejs/vue-test-plugin-i18n`](https://www.npmjs.com/package/@testforgejs/vue-test-plugin-i18n) — Vue I18n integration
297
824
  - [`@testforgejs/vue-test-plugin-router`](https://www.npmjs.com/package/@testforgejs/vue-test-plugin-router) — Vue Router integration
package/dist/index.cjs CHANGED
@@ -27,20 +27,17 @@ module.exports = __toCommonJS(index_exports);
27
27
  // src/presets.ts
28
28
  var import_vue_test_core = require("@testforgejs/vue-test-core");
29
29
  var import_vue_test_preset_base = require("@testforgejs/vue-test-preset-base");
30
+ var import_vue_test_plugin_pinia = require("@testforgejs/vue-test-plugin-pinia");
30
31
  var import_vitest = require("vitest");
31
- var defaultVitestPinia = (() => ({
32
- ...(0, import_vue_test_preset_base.defaultPinia)(),
33
- createSpy: import_vitest.vi.fn
34
- }));
35
32
  var presets = {
36
33
  default: (0, import_vue_test_core.extendPreset)(import_vue_test_preset_base.presets.default, {
37
34
  defaults: {
38
- pinia: defaultVitestPinia
35
+ [import_vue_test_plugin_pinia.PLUGIN_NAME]: import_vue_test_plugin_pinia.piniaPlugin.getDefaultOptions(import_vitest.vi)
39
36
  }
40
37
  }),
41
38
  piniaPreset: (0, import_vue_test_core.extendPreset)(import_vue_test_preset_base.presets.piniaPreset, {
42
39
  defaults: {
43
- pinia: defaultVitestPinia
40
+ [import_vue_test_plugin_pinia.PLUGIN_NAME]: import_vue_test_plugin_pinia.piniaPlugin.getDefaultOptions(import_vitest.vi)
44
41
  }
45
42
  }),
46
43
  i18nPreset: import_vue_test_preset_base.presets.i18nPreset,
package/dist/index.d.cts CHANGED
@@ -3,27 +3,26 @@ import * as vue_router from 'vue-router';
3
3
  import * as _testforgejs_vue_test_plugin_i18n from '@testforgejs/vue-test-plugin-i18n';
4
4
  import * as vue_i18n from 'vue-i18n';
5
5
  import * as _testforgejs_vue_test_core from '@testforgejs/vue-test-core';
6
- import { PluginOptionsFactory } from '@testforgejs/vue-test-core';
7
6
 
8
7
  declare const presets: {
9
8
  default: _testforgejs_vue_test_core.PresetDefinition;
10
9
  piniaPreset: _testforgejs_vue_test_core.PresetDefinition;
11
10
  i18nPreset: {
12
11
  manifest: {
13
- module: _testforgejs_vue_test_core.PluginModule<vue_i18n.I18n<{}, {}, {}, string, boolean>, _testforgejs_vue_test_plugin_i18n.VueTestI18nOptions>;
12
+ module: _testforgejs_vue_test_core.PluginModuleWithDefaults<vue_i18n.I18n<{}, {}, {}, string, boolean>, _testforgejs_vue_test_plugin_i18n.VueTestI18nOptions>;
14
13
  enabled: true;
15
14
  }[];
16
15
  defaults: {
17
- i18n: PluginOptionsFactory<_testforgejs_vue_test_plugin_i18n.VueTestI18nOptions>;
16
+ i18n: _testforgejs_vue_test_core.PluginOptionsFactory<_testforgejs_vue_test_plugin_i18n.VueTestI18nOptions>;
18
17
  };
19
18
  };
20
19
  routerPreset: {
21
20
  manifest: {
22
- module: _testforgejs_vue_test_core.PluginModule<vue_router._RouterClassic, _testforgejs_vue_test_plugin_router.VueTestRouterOptions>;
21
+ module: _testforgejs_vue_test_core.PluginModuleWithDefaults<vue_router._RouterClassic, _testforgejs_vue_test_plugin_router.VueTestRouterOptions>;
23
22
  enabled: true;
24
23
  }[];
25
24
  defaults: {
26
- router: PluginOptionsFactory<_testforgejs_vue_test_plugin_router.VueTestRouterOptions>;
25
+ router: _testforgejs_vue_test_core.PluginOptionsFactory<_testforgejs_vue_test_plugin_router.VueTestRouterOptions>;
27
26
  };
28
27
  };
29
28
  };
package/dist/index.d.ts CHANGED
@@ -3,27 +3,26 @@ import * as vue_router from 'vue-router';
3
3
  import * as _testforgejs_vue_test_plugin_i18n from '@testforgejs/vue-test-plugin-i18n';
4
4
  import * as vue_i18n from 'vue-i18n';
5
5
  import * as _testforgejs_vue_test_core from '@testforgejs/vue-test-core';
6
- import { PluginOptionsFactory } from '@testforgejs/vue-test-core';
7
6
 
8
7
  declare const presets: {
9
8
  default: _testforgejs_vue_test_core.PresetDefinition;
10
9
  piniaPreset: _testforgejs_vue_test_core.PresetDefinition;
11
10
  i18nPreset: {
12
11
  manifest: {
13
- module: _testforgejs_vue_test_core.PluginModule<vue_i18n.I18n<{}, {}, {}, string, boolean>, _testforgejs_vue_test_plugin_i18n.VueTestI18nOptions>;
12
+ module: _testforgejs_vue_test_core.PluginModuleWithDefaults<vue_i18n.I18n<{}, {}, {}, string, boolean>, _testforgejs_vue_test_plugin_i18n.VueTestI18nOptions>;
14
13
  enabled: true;
15
14
  }[];
16
15
  defaults: {
17
- i18n: PluginOptionsFactory<_testforgejs_vue_test_plugin_i18n.VueTestI18nOptions>;
16
+ i18n: _testforgejs_vue_test_core.PluginOptionsFactory<_testforgejs_vue_test_plugin_i18n.VueTestI18nOptions>;
18
17
  };
19
18
  };
20
19
  routerPreset: {
21
20
  manifest: {
22
- module: _testforgejs_vue_test_core.PluginModule<vue_router._RouterClassic, _testforgejs_vue_test_plugin_router.VueTestRouterOptions>;
21
+ module: _testforgejs_vue_test_core.PluginModuleWithDefaults<vue_router._RouterClassic, _testforgejs_vue_test_plugin_router.VueTestRouterOptions>;
23
22
  enabled: true;
24
23
  }[];
25
24
  defaults: {
26
- router: PluginOptionsFactory<_testforgejs_vue_test_plugin_router.VueTestRouterOptions>;
25
+ router: _testforgejs_vue_test_core.PluginOptionsFactory<_testforgejs_vue_test_plugin_router.VueTestRouterOptions>;
27
26
  };
28
27
  };
29
28
  };
package/dist/index.js CHANGED
@@ -1,20 +1,17 @@
1
1
  // src/presets.ts
2
2
  import { extendPreset } from "@testforgejs/vue-test-core";
3
- import { defaultPinia, presets as basePresets } from "@testforgejs/vue-test-preset-base";
3
+ import { presets as basePresets } from "@testforgejs/vue-test-preset-base";
4
+ import { piniaPlugin, PLUGIN_NAME as PINIA_PLUGIN_NAME } from "@testforgejs/vue-test-plugin-pinia";
4
5
  import { vi } from "vitest";
5
- var defaultVitestPinia = (() => ({
6
- ...defaultPinia(),
7
- createSpy: vi.fn
8
- }));
9
6
  var presets = {
10
7
  default: extendPreset(basePresets.default, {
11
8
  defaults: {
12
- pinia: defaultVitestPinia
9
+ [PINIA_PLUGIN_NAME]: piniaPlugin.getDefaultOptions(vi)
13
10
  }
14
11
  }),
15
12
  piniaPreset: extendPreset(basePresets.piniaPreset, {
16
13
  defaults: {
17
- pinia: defaultVitestPinia
14
+ [PINIA_PLUGIN_NAME]: piniaPlugin.getDefaultOptions(vi)
18
15
  }
19
16
  }),
20
17
  i18nPreset: basePresets.i18nPreset,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testforgejs/vue-test-preset-recommended",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "type": "module",
5
5
  "description": "Official recommended preset for getting started with the TestForge framework",
6
6
  "homepage": "https://github.com/testforgejs/testforge#readme",
@@ -44,19 +44,19 @@
44
44
  "license": "MIT",
45
45
  "author": "Roman Levchenko",
46
46
  "dependencies": {
47
- "@testforgejs/vue-test-plugin-i18n": "1.0.0-beta.1",
48
- "@testforgejs/vue-test-plugin-pinia": "1.0.0-beta.1",
49
- "@testforgejs/vue-test-plugin-router": "1.0.0-beta.1"
47
+ "@testforgejs/vue-test-plugin-i18n": "1.0.0-beta.3",
48
+ "@testforgejs/vue-test-plugin-router": "1.0.0-beta.3",
49
+ "@testforgejs/vue-test-plugin-pinia": "1.0.0-beta.3"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "vitest": "^3.0.0 || ^4.0.0",
53
- "@testforgejs/vue-test-core": "1.0.0-beta.1",
54
- "@testforgejs/vue-test-preset-base": "1.0.0-beta.1"
53
+ "@testforgejs/vue-test-core": "1.0.0-beta.3",
54
+ "@testforgejs/vue-test-preset-base": "1.0.0-beta.3"
55
55
  },
56
56
  "devDependencies": {
57
57
  "vitest": "^3.0.0 || ^4.0.0",
58
- "@testforgejs/vue-test-core": "1.0.0-beta.1",
59
- "@testforgejs/vue-test-preset-base": "1.0.0-beta.1"
58
+ "@testforgejs/vue-test-core": "1.0.0-beta.3",
59
+ "@testforgejs/vue-test-preset-base": "1.0.0-beta.3"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "tsup",