@testforgejs/vue-test-core 1.0.0-beta.0 → 1.0.0-beta.2

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
@@ -1,18 +1,21 @@
1
1
  # @testforgejs/vue-test-core
2
2
 
3
- > Core runtime & declarative testing infrastructure for Vue 3
3
+ > Core runtime and declarative testing infrastructure for Vue 3.
4
4
 
5
5
  `@testforgejs/vue-test-core` is the core runtime of **TestForge**. It provides the framework for creating reusable, type-safe component test factories and coordinating managed Vue ecosystem plugins.
6
6
 
7
7
  The core package provides:
8
8
 
9
- - type-safe `testComponentFactory` instances
10
- - plugin registration and lifecycle management
11
- - project-level preset configuration
12
- - reusable component test factories
13
- - a hierarchical configuration merge pipeline (Preset → Factory → Test → Extra)
9
+ - type-safe `testComponentFactory` instances;
10
+ - plugin registration and lifecycle management;
11
+ - preset-based runtime configuration;
12
+ - reusable component test factories;
13
+ - a hierarchical configuration resolution pipeline:
14
+ **Preset → Factory → Test → Extra Options**.
14
15
 
15
- The core package itself does **not** include or configure any Vue ecosystem plugins. Plugins are provided by separate TestForge plugin packages and made available to the framework through a **preset**.
16
+ The core package itself does **not** include or configure Vue ecosystem plugins. Plugin integrations are provided by separate TestForge packages and become available to the framework through presets.
17
+
18
+ > Core defines the runtime. Plugins define integrations. Base presets define shared environments. Runner-specific recommended presets add runner-specific behavior. The host application provides Vue ecosystem dependencies.
16
19
 
17
20
  ## Installation
18
21
 
@@ -36,35 +39,139 @@ npm install -D @testforgejs/vue-test-core
36
39
  yarn add -D @testforgejs/vue-test-core
37
40
  ```
38
41
 
42
+ > [!NOTE]
43
+ > TestForge requires **[Vue](https://vuejs.org/) 3.3.0 or higher**.
44
+ >
45
+ > `vue` and `@vue/test-utils` are peer dependencies of `@testforgejs/vue-test-core`. Make sure they are installed in your project.
46
+
47
+ For most projects, you will also want to install a preset package that defines the managed Vue plugins available to your tests.
48
+
49
+ ---
50
+
39
51
  ## Presets
40
52
 
41
- A **preset** defines the managed plugins available to your TestForge runtime and provides their project-level default configuration.
53
+ A **preset** defines a TestForge runtime environment.
54
+
55
+ A preset controls:
56
+
57
+ - which managed plugins are available;
58
+ - which plugins are enabled by default;
59
+ - the default configuration for those plugins.
60
+
61
+ The preset system keeps the TestForge core independent from specific Vue ecosystem integrations. The core runtime does not need built-in knowledge of Pinia, Vue Router, Vue I18n, Vuetify, PrimeVue, or other integrations.
62
+
63
+ ### Single and Multiple Presets
64
+
65
+ `createTestFramework()` supports two mutually exclusive ways to configure presets.
42
66
 
43
- The preset system separates the TestForge core from individual Vue ecosystem integrations. This means the core package does not need to know about Pinia, Vue Router, Vue I18n, Vuetify, PrimeVue, or other integrations unless they are explicitly registered through a preset.
67
+ Use `preset` when the framework needs a single runtime environment:
44
68
 
45
- TestForge provides an official recommended preset:
69
+ ```typescript
70
+ createTestFramework({
71
+ preset: projectPreset,
72
+ });
73
+ ```
74
+
75
+ Use `presets` when the framework should provide multiple named runtime environments:
76
+
77
+ ```typescript
78
+ createTestFramework({
79
+ presets: {
80
+ default: projectPreset,
81
+ i18n: i18nPreset,
82
+ router: routerPreset,
83
+ },
84
+ });
85
+ ```
86
+
87
+ These options cannot be used together.
88
+
89
+ A single `preset` is treated as the framework's `default` preset internally. A `presets` registry allows individual factory invocations to select a named preset through `extraOptions.preset`.
90
+
91
+ For example:
92
+
93
+ ```typescript
94
+ const { testComponentFactory } = createTestFramework({
95
+ presets: {
96
+ default: defaultPreset,
97
+ i18n: i18nPreset,
98
+ },
99
+ });
100
+
101
+ const factory = testComponentFactory(MyComponent);
102
+
103
+ factory(
104
+ {},
105
+ {},
106
+ {},
107
+ {
108
+ preset: "i18n",
109
+ },
110
+ );
111
+ ```
112
+
113
+ This distinction allows a project to start with a single custom runtime environment and introduce named runtime profiles later without changing the preset definition itself.
114
+
115
+ ### Official preset packages
116
+
117
+ TestForge provides a layered preset architecture:
118
+
119
+ ```text
120
+ @testforgejs/vue-test-preset-base
121
+
122
+ ├── @testforgejs/vue-test-preset-recommended
123
+ │ Vitest defaults
124
+
125
+ └── @testforgejs/vue-test-preset-recommended-jest
126
+ Jest defaults
127
+ ```
128
+
129
+ `@testforgejs/vue-test-preset-base` provides shared Vue plugin configuration.
130
+
131
+ Runner-specific recommended presets build on top of the base presets and add configuration required by a particular test runner.
132
+
133
+ For example, the recommended Vitest preset provides `vi.fn` as the Pinia `createSpy` implementation.
134
+
135
+ ### Recommended presets
136
+
137
+ For Vitest:
46
138
 
47
139
  ```bash
48
140
  pnpm add -D @testforgejs/vue-test-preset-recommended
49
141
  ```
50
142
 
51
- The recommended preset provides commonly used integrations such as:
143
+ For Jest:
144
+
145
+ ```bash
146
+ pnpm add -D @testforgejs/vue-test-preset-recommended-jest
147
+ ```
148
+
149
+ The recommended presets include configurations for commonly used managed integrations such as:
150
+
151
+ - Pinia;
152
+ - Vue I18n;
153
+ - Vue Router.
52
154
 
53
- - Pinia
54
- - Vue Router
55
- - Vue I18n
155
+ The recommended preset is a convenient starting point for exploring TestForge and its approach to reusable component test environments.
56
156
 
57
- Additional integrations, such as Vuetify and PrimeVue, are available as separate plugin packages and can be included in your own preset configuration.
157
+ However, recommended presets are intentionally generic. Real applications usually have application-specific routes, managed plugins, and plugin defaults. Most projects will therefore eventually benefit from a project-specific preset that extends or adapts an existing preset to the application's runtime environment.
158
+
159
+ The recommended preset packages are optional. You can also use the base preset directly or create your own project-specific preset.
58
160
 
59
161
  > [!TIP]
60
- > The recommended preset is optional. If your project requires a different set of plugins or configurations, you can create a custom preset.
162
+ > If you are getting started with TestForge, use the recommended preset that matches your test runner.
163
+
164
+ See the [Getting Started Guide](https://github.com/testforgejs/testforge/blob/main/docs/getting-started.md) for the recommended setup and the [Preset Authoring Guide](https://github.com/testforgejs/testforge/blob/main/docs/preset-authoring-guide.md) for creating and composing custom presets.
61
165
 
62
- 👉 See the [Getting Started Guide](https://github.com/testforgejs/testforge/blob/main/docs/getting-started.md) for the recommended setup and the [Preset Authoring Guide](https://github.com/testforgejs/testforge/blob/main/docs/preset-authoring-guide.md) for custom presets.
166
+ ---
63
167
 
64
168
  ## Quick Usage
65
169
 
170
+ The following example uses Vitest and the recommended Vitest preset registry.
171
+
66
172
  ```typescript
67
- // tests/setup.ts (or any other initialization file in your project)
173
+ // tests/setup.ts
174
+
68
175
  import { createTestFramework } from "@testforgejs/vue-test-core";
69
176
  import { presets } from "@testforgejs/vue-test-preset-recommended";
70
177
 
@@ -75,44 +182,84 @@ const { testComponentFactory } = createTestFramework({
75
182
  export { testComponentFactory };
76
183
  ```
77
184
 
185
+ This setup is a good starting point for exploring TestForge. As the application-specific testing environment grows, the project can define its own preset while continuing to reuse the official preset as a base.
186
+
78
187
  The resulting `testComponentFactory` can then be imported and reused throughout your component tests.
79
188
 
80
189
  ```typescript
81
- // MyComponent.spec.ts (example component test)
190
+ // MyComponent.spec.ts
191
+
192
+ import { describe, expect, it } from "vitest";
193
+
82
194
  import { testComponentFactory } from "@/tests/setup";
83
195
  import MyComponent from "@/components/MyComponent.vue";
84
196
 
85
197
  const factory = testComponentFactory(MyComponent);
86
198
 
87
- test("renders correctly", () => {
88
- const wrapper = factory();
199
+ describe("MyComponent.vue", () => {
200
+ it("renders correctly", () => {
201
+ const wrapper = factory();
89
202
 
90
- expect(wrapper.exists()).toBe(true);
203
+ expect(wrapper.exists()).toBe(true);
204
+ });
91
205
  });
92
206
  ```
93
207
 
208
+ For Jest projects, use `@testforgejs/vue-test-preset-recommended-jest` instead.
209
+
94
210
  👉 For a complete walkthrough, continue with the [Getting Started Guide](https://github.com/testforgejs/testforge/blob/main/docs/getting-started.md).
95
211
 
212
+ ---
213
+
214
+ ## Project-Specific Presets
215
+
216
+ Most applications will eventually benefit from some application-specific preset configuration.
217
+
218
+ For example, a project can extend the recommended preset with its own locale configuration:
219
+
220
+ ```typescript
221
+ import { extendPreset } from "@testforgejs/vue-test-core";
222
+ import { presets as recommendedPresets } from "@testforgejs/vue-test-preset-recommended";
223
+
224
+ const projectPreset = extendPreset(recommendedPresets.default, {
225
+ defaults: {
226
+ i18n: () => ({
227
+ ...recommendedPresets.default.defaults.i18n(),
228
+ locale: "uk",
229
+ }),
230
+ },
231
+ });
232
+ ```
233
+
234
+ ---
235
+
96
236
  ## Documentation
97
237
 
98
238
  - [Getting Started Guide](https://github.com/testforgejs/testforge/blob/main/docs/getting-started.md) — Set up TestForge in a project and create reusable component test factories.
99
- - [Configuration & Advanced Usage](https://github.com/testforgejs/testforge/blob/main/docs/configuration.md) — Learn about configuration layers, merge strategies, execution flags, and plugin lifecycle behavior.
239
+ - [Configuration & Advanced Usage](https://github.com/testforgejs/testforge/blob/main/docs/configuration.md) — Learn about configuration layers, merge strategies, execution controls, and plugin lifecycle behavior.
240
+ - [Preset Authoring Guide](https://github.com/testforgejs/testforge/blob/main/docs/preset-authoring-guide.md) — Create and compose custom presets.
100
241
  - [Plugin Authoring Guide](https://github.com/testforgejs/testforge/blob/main/docs/plugin-authoring-guide.md) — Create custom TestForge plugins.
101
- - [Preset Authoring Guide](https://github.com/testforgejs/testforge/blob/main/docs/preset-authoring-guide.md) — Create custom presets for your project or organization.
102
242
 
103
243
  ## Project
104
244
 
105
245
  This package is part of the **TestForge** monorepo.
106
246
 
107
- - **[TestForge Project Overview](https://github.com/testforgejs/testforge#readme)** — project overview, package ecosystem, roadmap and repository information.
247
+ - **[TestForge Project Overview](https://github.com/testforgejs/testforge#readme)** — Project overview, package ecosystem, roadmap, and repository information.
108
248
  - **Repository:** https://github.com/testforgejs/testforge
109
249
 
110
- ## Related packages
250
+ ## Related Packages
251
+
252
+ ### Presets
111
253
 
254
+ - `@testforgejs/vue-test-preset-base`
112
255
  - `@testforgejs/vue-test-preset-recommended`
113
- - `@testforgejs/vue-test-plugin-router`
256
+ - `@testforgejs/vue-test-preset-recommended-jest`
257
+
258
+ ### Managed Plugins
259
+
114
260
  - `@testforgejs/vue-test-plugin-pinia`
115
261
  - `@testforgejs/vue-test-plugin-i18n`
262
+ - `@testforgejs/vue-test-plugin-router`
116
263
  - `@testforgejs/vue-test-plugin-vuetify`
117
264
  - `@testforgejs/vue-test-plugin-primevue`
118
265
 
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  createPluginInstance: () => createPluginInstance,
26
26
  createTestFramework: () => createTestFramework,
27
27
  createVuePlugin: () => createVuePlugin,
28
+ extendPreset: () => extendPreset,
28
29
  validatePreset: () => validatePreset,
29
30
  validatePresets: () => validatePresets
30
31
  });
@@ -55,16 +56,29 @@ var FRAMEWORK_NAME = "TestForge";
55
56
  var ERROR_PREFIX = `[${FRAMEWORK_NAME}]`;
56
57
  var DEFAULT_PRESET_NAME = "default";
57
58
 
58
- // src/validators/validatePreset.ts
59
+ // src/guards/isPlainObject.ts
60
+ function isPlainObject(item) {
61
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
62
+ return false;
63
+ }
64
+ const prototype = Object.getPrototypeOf(item);
65
+ return prototype === Object.prototype || prototype === null;
66
+ }
67
+
68
+ // src/presets/validators/validatePreset.ts
59
69
  function validatePreset(name, preset) {
60
70
  if (!preset) {
61
71
  throw new Error(`${ERROR_PREFIX} Preset "${name}" is null or undefined.`);
62
72
  }
63
- if (!Array.isArray(preset.manifest)) {
73
+ if (!isPlainObject(preset)) {
74
+ throw new Error(`${ERROR_PREFIX} Preset "${name}" must be a plain object.`);
75
+ }
76
+ const manifest = preset.manifest;
77
+ if (!Array.isArray(manifest)) {
64
78
  throw new Error(`${ERROR_PREFIX} Preset "${name}" must have a "manifest" array.`);
65
79
  }
66
80
  const manifestPluginNames = /* @__PURE__ */ new Set();
67
- preset.manifest.forEach((entry, index) => {
81
+ manifest.forEach((entry, index) => {
68
82
  const { module: module2, enabled } = entry;
69
83
  if (!module2 || typeof module2.getName !== "function") {
70
84
  throw new Error(`${ERROR_PREFIX} Invalid module at manifest[${index}] in preset "${name}".`);
@@ -82,26 +96,27 @@ function validatePreset(name, preset) {
82
96
  }
83
97
  manifestPluginNames.add(pluginName);
84
98
  });
85
- if (preset.defaults) {
86
- const defaultKeys = Object.keys(preset.defaults);
87
- defaultKeys.forEach((key) => {
88
- if (!manifestPluginNames.has(key)) {
89
- throw new Error(
90
- `${ERROR_PREFIX} Preset "${name}" contains defaults for unknown plugin "${key}". This plugin is not present in the manifest.`
91
- );
92
- }
93
- const value = preset.defaults[key];
94
- const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
95
- if (!isObject) {
96
- throw new Error(
97
- `${ERROR_PREFIX} Invalid default configuration for plugin "${key}" in preset "${name}". Expected Object, but received ${typeof value}.`
98
- );
99
- }
100
- });
99
+ const defaults = preset.defaults;
100
+ if (!isPlainObject(defaults)) {
101
+ throw new Error(`${ERROR_PREFIX} Preset "${name}" must have a "defaults" plain object.`);
101
102
  }
103
+ const defaultKeys = Object.keys(defaults);
104
+ defaultKeys.forEach((key) => {
105
+ if (!manifestPluginNames.has(key)) {
106
+ throw new Error(
107
+ `${ERROR_PREFIX} Preset "${name}" contains defaults for unknown plugin "${key}". This plugin is not present in the manifest.`
108
+ );
109
+ }
110
+ const value = defaults[key];
111
+ if (typeof value !== "function") {
112
+ throw new Error(
113
+ `${ERROR_PREFIX} Invalid default configuration for plugin "${key}" in preset "${name}". Expected a plugin options factory function, but received ${typeof value}.`
114
+ );
115
+ }
116
+ });
102
117
  }
103
118
 
104
- // src/utils/getActivePreset.ts
119
+ // src/presets/getActivePreset.ts
105
120
  function getActivePreset(presets = {}, extraOptions) {
106
121
  const requestedPresetName = extraOptions?.preset?.trim();
107
122
  const activeName = requestedPresetName || DEFAULT_PRESET_NAME;
@@ -331,15 +346,6 @@ var withBaseMountOptions = (ctx) => {
331
346
  });
332
347
  };
333
348
 
334
- // src/guards/isPlainObject.ts
335
- function isPlainObject(item) {
336
- if (item === null || typeof item !== "object" || Array.isArray(item)) {
337
- return false;
338
- }
339
- const prototype = Object.getPrototypeOf(item);
340
- return prototype === Object.prototype || prototype === null;
341
- }
342
-
343
349
  // src/utils/mergeConfigs.ts
344
350
  function mergeConfigs(target, source) {
345
351
  const arrays = Array.isArray(target) && Array.isArray(source);
@@ -416,12 +422,29 @@ var withPluginsManifest = (ctx) => {
416
422
  });
417
423
  };
418
424
 
425
+ // src/presets/utils/buildPluginOptions.ts
426
+ function buildPluginOptions(pluginName, factory) {
427
+ const options = factory();
428
+ if (!isPlainObject(options)) {
429
+ throw new Error(
430
+ `${ERROR_PREFIX} Plugin options factory for "${pluginName}" must return a plain object.`
431
+ );
432
+ }
433
+ return options;
434
+ }
435
+
419
436
  // src/pipeline/middleware/transformers/withPreset.ts
420
437
  var withPreset = (ctx) => {
421
438
  const { preset } = ctx;
422
439
  if (!preset?.defaults) return ctx;
440
+ const pluginDefaultsState = Object.fromEntries(
441
+ Object.entries(preset.defaults).map(([pluginName, factory]) => [
442
+ pluginName,
443
+ buildPluginOptions(pluginName, factory)
444
+ ])
445
+ );
423
446
  return patchResultState(ctx, {
424
- pluginDefaultsState: { ...preset.defaults }
447
+ pluginDefaultsState
425
448
  });
426
449
  };
427
450
 
@@ -625,8 +648,8 @@ function mountWithPlugins(component, ctx, overrides = {}, runtimeOptions = {
625
648
  });
626
649
  }
627
650
 
628
- // src/validators/validatePresets.ts
629
- function validatePresets(presets = {}) {
651
+ // src/presets/validators/validatePresets.ts
652
+ function validatePresets(presets) {
630
653
  if (!isPlainObject(presets)) {
631
654
  throw new Error(`${ERROR_PREFIX} Presets must be a plain object.`);
632
655
  }
@@ -635,8 +658,8 @@ function validatePresets(presets = {}) {
635
658
  });
636
659
  }
637
660
 
638
- // src/assertions/assertIsPlainObject.ts
639
- function assertIsPlainObject(value, name = "value") {
661
+ // src/assertions/assertIsPlainObjectValue.ts
662
+ function assertIsPlainObjectValue(value, name = "value") {
640
663
  if (!isPlainObject(value)) {
641
664
  throw new Error(`${name} must be a plain object.`);
642
665
  }
@@ -644,8 +667,14 @@ function assertIsPlainObject(value, name = "value") {
644
667
 
645
668
  // src/validators/validateCreateTestFrameworkOptions.ts
646
669
  function validateCreateTestFrameworkOptions(options = {}) {
647
- assertIsPlainObject(options, "createTestFramework options");
648
- const { shallowByDefault, presets } = options;
670
+ assertIsPlainObjectValue(options, "createTestFramework options");
671
+ const { preset, presets, shallowByDefault } = options;
672
+ if (preset !== void 0 && presets !== void 0) {
673
+ throw new Error(`${ERROR_PREFIX} "preset" and "presets" cannot be used together.`);
674
+ }
675
+ if (preset !== void 0) {
676
+ validatePreset("default", preset);
677
+ }
649
678
  if (presets !== void 0) {
650
679
  validatePresets(presets);
651
680
  }
@@ -748,10 +777,21 @@ function validateComponentFactoryArguments(props, mountOptions, slots, extraOpti
748
777
  validateComponentFactoryExtraOptions(extraOptions, presets);
749
778
  }
750
779
 
780
+ // src/core/utils/resolvePresets.ts
781
+ function resolvePresets(options) {
782
+ if (options.preset !== void 0) {
783
+ return {
784
+ default: options.preset
785
+ };
786
+ }
787
+ return options.presets ?? {};
788
+ }
789
+
751
790
  // src/core/createTestFramework.ts
752
791
  function createTestFramework(options = {}) {
753
792
  validateCreateTestFrameworkOptions(options);
754
- const { presets = {}, shallowByDefault = false } = options;
793
+ const { shallowByDefault = false } = options;
794
+ const presets = resolvePresets(options);
755
795
  const testComponentFactory = (component, defaultProps = {}, defaultMountOptions = {}, defaultSlots = {}) => {
756
796
  validateTestComponentFactoryArguments(
757
797
  component,
@@ -850,6 +890,95 @@ function captureInstance() {
850
890
  };
851
891
  }
852
892
 
893
+ // src/presets/validators/validatePluginDefaults.ts
894
+ function validatePluginDefaults(pluginName, value) {
895
+ if (typeof value !== "function") {
896
+ throw new Error(
897
+ `${ERROR_PREFIX} Invalid defaults for plugin "${pluginName}". Preset defaults must be provided as a plugin options factory function.`
898
+ );
899
+ }
900
+ }
901
+
902
+ // src/presets/validators/validatePresetExtension.ts
903
+ function validatePresetExtension(basePreset, extension) {
904
+ if (!basePreset) {
905
+ throw new Error(`${ERROR_PREFIX} Cannot extend a null or undefined preset.`);
906
+ }
907
+ if (!extension) {
908
+ throw new Error(`${ERROR_PREFIX} Preset extension is null or undefined.`);
909
+ }
910
+ const basePluginNames = new Set(basePreset.manifest.map((entry) => entry.module.getName()));
911
+ const extensionManifest = extension.manifest ?? [];
912
+ const extensionManifestPlugins = /* @__PURE__ */ new Set();
913
+ for (const entry of extensionManifest) {
914
+ const pluginName = entry.module.getName();
915
+ if (extensionManifestPlugins.has(pluginName)) {
916
+ throw new Error(
917
+ `${ERROR_PREFIX} Duplicate plugin "${pluginName}" in preset extension manifest.`
918
+ );
919
+ }
920
+ extensionManifestPlugins.add(pluginName);
921
+ const isNewPlugin = !basePluginNames.has(pluginName);
922
+ if (isNewPlugin) {
923
+ if (entry.enabled === void 0) {
924
+ throw new Error(
925
+ `${ERROR_PREFIX} Cannot add plugin "${pluginName}" to an extended preset without specifying "enabled".`
926
+ );
927
+ }
928
+ const pluginDefaults = extension.defaults?.[pluginName];
929
+ if (pluginDefaults === void 0) {
930
+ throw new Error(
931
+ `${ERROR_PREFIX} Cannot add plugin "${pluginName}" to an extended preset without defining its defaults.`
932
+ );
933
+ }
934
+ }
935
+ }
936
+ if (extension.defaults) {
937
+ for (const [pluginName, pluginDefaults] of Object.entries(extension.defaults)) {
938
+ validatePluginDefaults(pluginName, pluginDefaults);
939
+ const isKnownPlugin = basePluginNames.has(pluginName) || extensionManifestPlugins.has(pluginName);
940
+ if (!isKnownPlugin) {
941
+ throw new Error(
942
+ `${ERROR_PREFIX} Cannot define defaults for unknown plugin "${pluginName}" in a preset extension. The plugin must be declared in the manifest.`
943
+ );
944
+ }
945
+ }
946
+ }
947
+ }
948
+
949
+ // src/presets/extendPreset.ts
950
+ function extendPreset(basePreset, extension) {
951
+ validatePresetExtension(basePreset, extension);
952
+ const manifest = basePreset.manifest.map((entry) => ({ ...entry }));
953
+ const defaults = { ...basePreset.defaults };
954
+ const manifestByName = new Map(manifest.map((entry) => [entry.module.getName(), entry]));
955
+ const extensionManifest = extension.manifest ?? [];
956
+ for (const entry of extensionManifest) {
957
+ const pluginName = entry.module.getName();
958
+ const existingEntry = manifestByName.get(pluginName);
959
+ if (existingEntry) {
960
+ if (entry.enabled !== void 0) {
961
+ existingEntry.enabled = entry.enabled;
962
+ }
963
+ } else {
964
+ manifest.push({
965
+ module: entry.module,
966
+ enabled: entry.enabled
967
+ });
968
+ manifestByName.set(pluginName, manifest[manifest.length - 1]);
969
+ }
970
+ }
971
+ if (extension.defaults) {
972
+ for (const [pluginName, pluginDefaults] of Object.entries(extension.defaults)) {
973
+ defaults[pluginName] = pluginDefaults;
974
+ }
975
+ }
976
+ return {
977
+ manifest,
978
+ defaults
979
+ };
980
+ }
981
+
853
982
  // src/index.ts
854
983
  var Types = {};
855
984
  // Annotate the CommonJS export names for ESM import in node:
@@ -859,6 +988,7 @@ var Types = {};
859
988
  createPluginInstance,
860
989
  createTestFramework,
861
990
  createVuePlugin,
991
+ extendPreset,
862
992
  validatePreset,
863
993
  validatePresets
864
994
  });
package/dist/index.d.cts CHANGED
@@ -29,7 +29,14 @@ type SupportedPluginsMap = Record<PluginName, SupportedPluginState>;
29
29
  type RuntimePluginConfig = Record<string, any>;
30
30
  type RuntimePluginOption = RuntimePluginConfig | false;
31
31
  type ResolvedPluginOptions = Record<PluginName, RuntimePluginOption>;
32
- type PluginConfigDefaults = Record<PluginName, RuntimePluginConfig>;
32
+ /**
33
+ * Default plugin configuration factories.
34
+ *
35
+ * Only plugins that require preset-level default configuration
36
+ * need to be included.
37
+ */
38
+ type PluginConfigDefaults = Record<PluginName, PluginOptionsFactory<RuntimePluginConfig>>;
39
+ type ResolvedPluginDefaults = Record<PluginName, RuntimePluginConfig>;
33
40
  interface PluginRuntimeMeta<TInstance> {
34
41
  __sharedInstance?: TInstance;
35
42
  expose?: (instance: TInstance) => void;
@@ -97,10 +104,23 @@ type ComponentFactoryExtraOptions = {
97
104
  * `plugin.getName()` for plugins declared in `manifest`.
98
105
  */
99
106
  interface PresetDefinition {
107
+ /** Plugins available to the preset and their enabled state. */
100
108
  manifest: PluginManifestEntry<any, any>[];
109
+ /**
110
+ * Default plugin configurations.
111
+ *
112
+ * A plugin declared in `manifest` does not have to be present here.
113
+ * Missing defaults mean that the preset provides no default configuration
114
+ * for that plugin.
115
+ */
101
116
  defaults: PluginConfigDefaults;
102
117
  }
103
118
  type TestFrameworkPresets = Record<string, PresetDefinition>;
119
+ interface PresetExtension {
120
+ manifest?: PluginManifestEntry<any, any>[];
121
+ defaults?: PluginConfigDefaults;
122
+ }
123
+ type PluginOptionsFactory<TOptions> = () => TOptions;
104
124
  interface PipelineContext {
105
125
  defaultMountOptions: ComponentFactoryOptions;
106
126
  mountOptions: ComponentFactoryOptions;
@@ -119,7 +139,7 @@ type MountOptionsState = Partial<MountingOptions<any, any>>;
119
139
  interface PipelineContextResult {
120
140
  mountOptions: MountOptionsState;
121
141
  global: NonNullable<MountingOptions<any, any>["global"]>;
122
- pluginDefaultsState: PluginConfigDefaults;
142
+ pluginDefaultsState: ResolvedPluginDefaults;
123
143
  plugins: ResolvedPluginOptions;
124
144
  }
125
145
  /**
@@ -154,9 +174,7 @@ type ComponentDataInput<T extends Component> = Partial<ComponentData<T>>;
154
174
  * instead of maintaining a parallel wrapper type hierarchy.
155
175
  */
156
176
  type ComponentFactory<T extends Component> = (props?: ComponentPropsInput<T>, mountOptions?: ComponentFactoryOptions<ComponentPropsInput<T>, ComponentSlotsInput<T>, ComponentDataInput<T>>, slots?: ComponentSlotsInput<T>, extraOptions?: ComponentFactoryExtraOptions) => ReturnType<typeof mount<T>>;
157
- interface CreateTestFrameworkOptions {
158
- /** Preset configurations for plugins */
159
- presets?: TestFrameworkPresets;
177
+ interface CommonCreateTestFrameworkOptions {
160
178
  /**
161
179
  * Default value for Vue Test Utils `shallow` mounting.
162
180
  *
@@ -167,6 +185,17 @@ interface CreateTestFrameworkOptions {
167
185
  */
168
186
  shallowByDefault?: boolean;
169
187
  }
188
+ interface SinglePresetOptions extends CommonCreateTestFrameworkOptions {
189
+ /** Single preset used by the framework. */
190
+ preset: PresetDefinition;
191
+ presets?: never;
192
+ }
193
+ interface MultiplePresetsOptions extends CommonCreateTestFrameworkOptions {
194
+ /** Named preset configurations for plugins. */
195
+ preset?: never;
196
+ presets?: TestFrameworkPresets;
197
+ }
198
+ type CreateTestFrameworkOptions = SinglePresetOptions | MultiplePresetsOptions;
170
199
  interface ComponentFactoryCreator {
171
200
  <T extends Component>(component: T, defaultProps?: ComponentPropsInput<T>, defaultMountOptions?: ComponentFactoryOptions<ComponentPropsInput<T>, ComponentSlotsInput<T>, ComponentDataInput<T>>, defaultSlots?: ComponentSlotsInput<T>): ComponentFactory<T>;
172
201
  }
@@ -185,10 +214,12 @@ declare function createVuePlugin<TPlugin extends Plugin, TOptions extends object
185
214
 
186
215
  declare function captureInstance<T = unknown>(): InstanceCapture<T>;
187
216
 
188
- declare function validatePreset(name: PluginName, preset: PresetDefinition): void;
217
+ declare function extendPreset(basePreset: PresetDefinition, extension: PresetExtension): PresetDefinition;
218
+
219
+ declare function validatePreset(name: string, preset: unknown): asserts preset is PresetDefinition;
189
220
 
190
- declare function validatePresets(presets?: TestFrameworkPresets): void;
221
+ declare function validatePresets(presets: unknown): asserts presets is TestFrameworkPresets;
191
222
 
192
223
  declare const Types: {};
193
224
 
194
- export { type ComponentFactory, type ComponentFactoryCreator, type ComponentFactoryExtraOptions, type ComponentFactoryOptions, type MountPlugin, type PluginControlOptions, type PluginModule, type PluginOptionsInput, type PluginOptionsMap, type PluginOverridesInput, type PresetDefinition, type TestFramework, type TestFrameworkPresets, Types, captureInstance, createPluginInstance, createTestFramework, createVuePlugin, validatePreset, validatePresets };
225
+ export { type ComponentFactory, type ComponentFactoryCreator, type ComponentFactoryExtraOptions, type ComponentFactoryOptions, type CreateTestFrameworkOptions, type MountPlugin, type PluginControlOptions, type PluginManifestEntry, type PluginModule, type PluginOptionsFactory, type PluginOptionsInput, type PluginOptionsMap, type PluginOverridesInput, type PresetDefinition, type PresetExtension, type TestFramework, type TestFrameworkPresets, Types, captureInstance, createPluginInstance, createTestFramework, createVuePlugin, extendPreset, validatePreset, validatePresets };
package/dist/index.d.ts CHANGED
@@ -29,7 +29,14 @@ type SupportedPluginsMap = Record<PluginName, SupportedPluginState>;
29
29
  type RuntimePluginConfig = Record<string, any>;
30
30
  type RuntimePluginOption = RuntimePluginConfig | false;
31
31
  type ResolvedPluginOptions = Record<PluginName, RuntimePluginOption>;
32
- type PluginConfigDefaults = Record<PluginName, RuntimePluginConfig>;
32
+ /**
33
+ * Default plugin configuration factories.
34
+ *
35
+ * Only plugins that require preset-level default configuration
36
+ * need to be included.
37
+ */
38
+ type PluginConfigDefaults = Record<PluginName, PluginOptionsFactory<RuntimePluginConfig>>;
39
+ type ResolvedPluginDefaults = Record<PluginName, RuntimePluginConfig>;
33
40
  interface PluginRuntimeMeta<TInstance> {
34
41
  __sharedInstance?: TInstance;
35
42
  expose?: (instance: TInstance) => void;
@@ -97,10 +104,23 @@ type ComponentFactoryExtraOptions = {
97
104
  * `plugin.getName()` for plugins declared in `manifest`.
98
105
  */
99
106
  interface PresetDefinition {
107
+ /** Plugins available to the preset and their enabled state. */
100
108
  manifest: PluginManifestEntry<any, any>[];
109
+ /**
110
+ * Default plugin configurations.
111
+ *
112
+ * A plugin declared in `manifest` does not have to be present here.
113
+ * Missing defaults mean that the preset provides no default configuration
114
+ * for that plugin.
115
+ */
101
116
  defaults: PluginConfigDefaults;
102
117
  }
103
118
  type TestFrameworkPresets = Record<string, PresetDefinition>;
119
+ interface PresetExtension {
120
+ manifest?: PluginManifestEntry<any, any>[];
121
+ defaults?: PluginConfigDefaults;
122
+ }
123
+ type PluginOptionsFactory<TOptions> = () => TOptions;
104
124
  interface PipelineContext {
105
125
  defaultMountOptions: ComponentFactoryOptions;
106
126
  mountOptions: ComponentFactoryOptions;
@@ -119,7 +139,7 @@ type MountOptionsState = Partial<MountingOptions<any, any>>;
119
139
  interface PipelineContextResult {
120
140
  mountOptions: MountOptionsState;
121
141
  global: NonNullable<MountingOptions<any, any>["global"]>;
122
- pluginDefaultsState: PluginConfigDefaults;
142
+ pluginDefaultsState: ResolvedPluginDefaults;
123
143
  plugins: ResolvedPluginOptions;
124
144
  }
125
145
  /**
@@ -154,9 +174,7 @@ type ComponentDataInput<T extends Component> = Partial<ComponentData<T>>;
154
174
  * instead of maintaining a parallel wrapper type hierarchy.
155
175
  */
156
176
  type ComponentFactory<T extends Component> = (props?: ComponentPropsInput<T>, mountOptions?: ComponentFactoryOptions<ComponentPropsInput<T>, ComponentSlotsInput<T>, ComponentDataInput<T>>, slots?: ComponentSlotsInput<T>, extraOptions?: ComponentFactoryExtraOptions) => ReturnType<typeof mount<T>>;
157
- interface CreateTestFrameworkOptions {
158
- /** Preset configurations for plugins */
159
- presets?: TestFrameworkPresets;
177
+ interface CommonCreateTestFrameworkOptions {
160
178
  /**
161
179
  * Default value for Vue Test Utils `shallow` mounting.
162
180
  *
@@ -167,6 +185,17 @@ interface CreateTestFrameworkOptions {
167
185
  */
168
186
  shallowByDefault?: boolean;
169
187
  }
188
+ interface SinglePresetOptions extends CommonCreateTestFrameworkOptions {
189
+ /** Single preset used by the framework. */
190
+ preset: PresetDefinition;
191
+ presets?: never;
192
+ }
193
+ interface MultiplePresetsOptions extends CommonCreateTestFrameworkOptions {
194
+ /** Named preset configurations for plugins. */
195
+ preset?: never;
196
+ presets?: TestFrameworkPresets;
197
+ }
198
+ type CreateTestFrameworkOptions = SinglePresetOptions | MultiplePresetsOptions;
170
199
  interface ComponentFactoryCreator {
171
200
  <T extends Component>(component: T, defaultProps?: ComponentPropsInput<T>, defaultMountOptions?: ComponentFactoryOptions<ComponentPropsInput<T>, ComponentSlotsInput<T>, ComponentDataInput<T>>, defaultSlots?: ComponentSlotsInput<T>): ComponentFactory<T>;
172
201
  }
@@ -185,10 +214,12 @@ declare function createVuePlugin<TPlugin extends Plugin, TOptions extends object
185
214
 
186
215
  declare function captureInstance<T = unknown>(): InstanceCapture<T>;
187
216
 
188
- declare function validatePreset(name: PluginName, preset: PresetDefinition): void;
217
+ declare function extendPreset(basePreset: PresetDefinition, extension: PresetExtension): PresetDefinition;
218
+
219
+ declare function validatePreset(name: string, preset: unknown): asserts preset is PresetDefinition;
189
220
 
190
- declare function validatePresets(presets?: TestFrameworkPresets): void;
221
+ declare function validatePresets(presets: unknown): asserts presets is TestFrameworkPresets;
191
222
 
192
223
  declare const Types: {};
193
224
 
194
- export { type ComponentFactory, type ComponentFactoryCreator, type ComponentFactoryExtraOptions, type ComponentFactoryOptions, type MountPlugin, type PluginControlOptions, type PluginModule, type PluginOptionsInput, type PluginOptionsMap, type PluginOverridesInput, type PresetDefinition, type TestFramework, type TestFrameworkPresets, Types, captureInstance, createPluginInstance, createTestFramework, createVuePlugin, validatePreset, validatePresets };
225
+ export { type ComponentFactory, type ComponentFactoryCreator, type ComponentFactoryExtraOptions, type ComponentFactoryOptions, type CreateTestFrameworkOptions, type MountPlugin, type PluginControlOptions, type PluginManifestEntry, type PluginModule, type PluginOptionsFactory, type PluginOptionsInput, type PluginOptionsMap, type PluginOverridesInput, type PresetDefinition, type PresetExtension, type TestFramework, type TestFrameworkPresets, Types, captureInstance, createPluginInstance, createTestFramework, createVuePlugin, extendPreset, validatePreset, validatePresets };
package/dist/index.js CHANGED
@@ -23,16 +23,29 @@ var FRAMEWORK_NAME = "TestForge";
23
23
  var ERROR_PREFIX = `[${FRAMEWORK_NAME}]`;
24
24
  var DEFAULT_PRESET_NAME = "default";
25
25
 
26
- // src/validators/validatePreset.ts
26
+ // src/guards/isPlainObject.ts
27
+ function isPlainObject(item) {
28
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
29
+ return false;
30
+ }
31
+ const prototype = Object.getPrototypeOf(item);
32
+ return prototype === Object.prototype || prototype === null;
33
+ }
34
+
35
+ // src/presets/validators/validatePreset.ts
27
36
  function validatePreset(name, preset) {
28
37
  if (!preset) {
29
38
  throw new Error(`${ERROR_PREFIX} Preset "${name}" is null or undefined.`);
30
39
  }
31
- if (!Array.isArray(preset.manifest)) {
40
+ if (!isPlainObject(preset)) {
41
+ throw new Error(`${ERROR_PREFIX} Preset "${name}" must be a plain object.`);
42
+ }
43
+ const manifest = preset.manifest;
44
+ if (!Array.isArray(manifest)) {
32
45
  throw new Error(`${ERROR_PREFIX} Preset "${name}" must have a "manifest" array.`);
33
46
  }
34
47
  const manifestPluginNames = /* @__PURE__ */ new Set();
35
- preset.manifest.forEach((entry, index) => {
48
+ manifest.forEach((entry, index) => {
36
49
  const { module, enabled } = entry;
37
50
  if (!module || typeof module.getName !== "function") {
38
51
  throw new Error(`${ERROR_PREFIX} Invalid module at manifest[${index}] in preset "${name}".`);
@@ -50,26 +63,27 @@ function validatePreset(name, preset) {
50
63
  }
51
64
  manifestPluginNames.add(pluginName);
52
65
  });
53
- if (preset.defaults) {
54
- const defaultKeys = Object.keys(preset.defaults);
55
- defaultKeys.forEach((key) => {
56
- if (!manifestPluginNames.has(key)) {
57
- throw new Error(
58
- `${ERROR_PREFIX} Preset "${name}" contains defaults for unknown plugin "${key}". This plugin is not present in the manifest.`
59
- );
60
- }
61
- const value = preset.defaults[key];
62
- const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
63
- if (!isObject) {
64
- throw new Error(
65
- `${ERROR_PREFIX} Invalid default configuration for plugin "${key}" in preset "${name}". Expected Object, but received ${typeof value}.`
66
- );
67
- }
68
- });
66
+ const defaults = preset.defaults;
67
+ if (!isPlainObject(defaults)) {
68
+ throw new Error(`${ERROR_PREFIX} Preset "${name}" must have a "defaults" plain object.`);
69
69
  }
70
+ const defaultKeys = Object.keys(defaults);
71
+ defaultKeys.forEach((key) => {
72
+ if (!manifestPluginNames.has(key)) {
73
+ throw new Error(
74
+ `${ERROR_PREFIX} Preset "${name}" contains defaults for unknown plugin "${key}". This plugin is not present in the manifest.`
75
+ );
76
+ }
77
+ const value = defaults[key];
78
+ if (typeof value !== "function") {
79
+ throw new Error(
80
+ `${ERROR_PREFIX} Invalid default configuration for plugin "${key}" in preset "${name}". Expected a plugin options factory function, but received ${typeof value}.`
81
+ );
82
+ }
83
+ });
70
84
  }
71
85
 
72
- // src/utils/getActivePreset.ts
86
+ // src/presets/getActivePreset.ts
73
87
  function getActivePreset(presets = {}, extraOptions) {
74
88
  const requestedPresetName = extraOptions?.preset?.trim();
75
89
  const activeName = requestedPresetName || DEFAULT_PRESET_NAME;
@@ -299,15 +313,6 @@ var withBaseMountOptions = (ctx) => {
299
313
  });
300
314
  };
301
315
 
302
- // src/guards/isPlainObject.ts
303
- function isPlainObject(item) {
304
- if (item === null || typeof item !== "object" || Array.isArray(item)) {
305
- return false;
306
- }
307
- const prototype = Object.getPrototypeOf(item);
308
- return prototype === Object.prototype || prototype === null;
309
- }
310
-
311
316
  // src/utils/mergeConfigs.ts
312
317
  function mergeConfigs(target, source) {
313
318
  const arrays = Array.isArray(target) && Array.isArray(source);
@@ -384,12 +389,29 @@ var withPluginsManifest = (ctx) => {
384
389
  });
385
390
  };
386
391
 
392
+ // src/presets/utils/buildPluginOptions.ts
393
+ function buildPluginOptions(pluginName, factory) {
394
+ const options = factory();
395
+ if (!isPlainObject(options)) {
396
+ throw new Error(
397
+ `${ERROR_PREFIX} Plugin options factory for "${pluginName}" must return a plain object.`
398
+ );
399
+ }
400
+ return options;
401
+ }
402
+
387
403
  // src/pipeline/middleware/transformers/withPreset.ts
388
404
  var withPreset = (ctx) => {
389
405
  const { preset } = ctx;
390
406
  if (!preset?.defaults) return ctx;
407
+ const pluginDefaultsState = Object.fromEntries(
408
+ Object.entries(preset.defaults).map(([pluginName, factory]) => [
409
+ pluginName,
410
+ buildPluginOptions(pluginName, factory)
411
+ ])
412
+ );
391
413
  return patchResultState(ctx, {
392
- pluginDefaultsState: { ...preset.defaults }
414
+ pluginDefaultsState
393
415
  });
394
416
  };
395
417
 
@@ -593,8 +615,8 @@ function mountWithPlugins(component, ctx, overrides = {}, runtimeOptions = {
593
615
  });
594
616
  }
595
617
 
596
- // src/validators/validatePresets.ts
597
- function validatePresets(presets = {}) {
618
+ // src/presets/validators/validatePresets.ts
619
+ function validatePresets(presets) {
598
620
  if (!isPlainObject(presets)) {
599
621
  throw new Error(`${ERROR_PREFIX} Presets must be a plain object.`);
600
622
  }
@@ -603,8 +625,8 @@ function validatePresets(presets = {}) {
603
625
  });
604
626
  }
605
627
 
606
- // src/assertions/assertIsPlainObject.ts
607
- function assertIsPlainObject(value, name = "value") {
628
+ // src/assertions/assertIsPlainObjectValue.ts
629
+ function assertIsPlainObjectValue(value, name = "value") {
608
630
  if (!isPlainObject(value)) {
609
631
  throw new Error(`${name} must be a plain object.`);
610
632
  }
@@ -612,8 +634,14 @@ function assertIsPlainObject(value, name = "value") {
612
634
 
613
635
  // src/validators/validateCreateTestFrameworkOptions.ts
614
636
  function validateCreateTestFrameworkOptions(options = {}) {
615
- assertIsPlainObject(options, "createTestFramework options");
616
- const { shallowByDefault, presets } = options;
637
+ assertIsPlainObjectValue(options, "createTestFramework options");
638
+ const { preset, presets, shallowByDefault } = options;
639
+ if (preset !== void 0 && presets !== void 0) {
640
+ throw new Error(`${ERROR_PREFIX} "preset" and "presets" cannot be used together.`);
641
+ }
642
+ if (preset !== void 0) {
643
+ validatePreset("default", preset);
644
+ }
617
645
  if (presets !== void 0) {
618
646
  validatePresets(presets);
619
647
  }
@@ -716,10 +744,21 @@ function validateComponentFactoryArguments(props, mountOptions, slots, extraOpti
716
744
  validateComponentFactoryExtraOptions(extraOptions, presets);
717
745
  }
718
746
 
747
+ // src/core/utils/resolvePresets.ts
748
+ function resolvePresets(options) {
749
+ if (options.preset !== void 0) {
750
+ return {
751
+ default: options.preset
752
+ };
753
+ }
754
+ return options.presets ?? {};
755
+ }
756
+
719
757
  // src/core/createTestFramework.ts
720
758
  function createTestFramework(options = {}) {
721
759
  validateCreateTestFrameworkOptions(options);
722
- const { presets = {}, shallowByDefault = false } = options;
760
+ const { shallowByDefault = false } = options;
761
+ const presets = resolvePresets(options);
723
762
  const testComponentFactory = (component, defaultProps = {}, defaultMountOptions = {}, defaultSlots = {}) => {
724
763
  validateTestComponentFactoryArguments(
725
764
  component,
@@ -818,6 +857,95 @@ function captureInstance() {
818
857
  };
819
858
  }
820
859
 
860
+ // src/presets/validators/validatePluginDefaults.ts
861
+ function validatePluginDefaults(pluginName, value) {
862
+ if (typeof value !== "function") {
863
+ throw new Error(
864
+ `${ERROR_PREFIX} Invalid defaults for plugin "${pluginName}". Preset defaults must be provided as a plugin options factory function.`
865
+ );
866
+ }
867
+ }
868
+
869
+ // src/presets/validators/validatePresetExtension.ts
870
+ function validatePresetExtension(basePreset, extension) {
871
+ if (!basePreset) {
872
+ throw new Error(`${ERROR_PREFIX} Cannot extend a null or undefined preset.`);
873
+ }
874
+ if (!extension) {
875
+ throw new Error(`${ERROR_PREFIX} Preset extension is null or undefined.`);
876
+ }
877
+ const basePluginNames = new Set(basePreset.manifest.map((entry) => entry.module.getName()));
878
+ const extensionManifest = extension.manifest ?? [];
879
+ const extensionManifestPlugins = /* @__PURE__ */ new Set();
880
+ for (const entry of extensionManifest) {
881
+ const pluginName = entry.module.getName();
882
+ if (extensionManifestPlugins.has(pluginName)) {
883
+ throw new Error(
884
+ `${ERROR_PREFIX} Duplicate plugin "${pluginName}" in preset extension manifest.`
885
+ );
886
+ }
887
+ extensionManifestPlugins.add(pluginName);
888
+ const isNewPlugin = !basePluginNames.has(pluginName);
889
+ if (isNewPlugin) {
890
+ if (entry.enabled === void 0) {
891
+ throw new Error(
892
+ `${ERROR_PREFIX} Cannot add plugin "${pluginName}" to an extended preset without specifying "enabled".`
893
+ );
894
+ }
895
+ const pluginDefaults = extension.defaults?.[pluginName];
896
+ if (pluginDefaults === void 0) {
897
+ throw new Error(
898
+ `${ERROR_PREFIX} Cannot add plugin "${pluginName}" to an extended preset without defining its defaults.`
899
+ );
900
+ }
901
+ }
902
+ }
903
+ if (extension.defaults) {
904
+ for (const [pluginName, pluginDefaults] of Object.entries(extension.defaults)) {
905
+ validatePluginDefaults(pluginName, pluginDefaults);
906
+ const isKnownPlugin = basePluginNames.has(pluginName) || extensionManifestPlugins.has(pluginName);
907
+ if (!isKnownPlugin) {
908
+ throw new Error(
909
+ `${ERROR_PREFIX} Cannot define defaults for unknown plugin "${pluginName}" in a preset extension. The plugin must be declared in the manifest.`
910
+ );
911
+ }
912
+ }
913
+ }
914
+ }
915
+
916
+ // src/presets/extendPreset.ts
917
+ function extendPreset(basePreset, extension) {
918
+ validatePresetExtension(basePreset, extension);
919
+ const manifest = basePreset.manifest.map((entry) => ({ ...entry }));
920
+ const defaults = { ...basePreset.defaults };
921
+ const manifestByName = new Map(manifest.map((entry) => [entry.module.getName(), entry]));
922
+ const extensionManifest = extension.manifest ?? [];
923
+ for (const entry of extensionManifest) {
924
+ const pluginName = entry.module.getName();
925
+ const existingEntry = manifestByName.get(pluginName);
926
+ if (existingEntry) {
927
+ if (entry.enabled !== void 0) {
928
+ existingEntry.enabled = entry.enabled;
929
+ }
930
+ } else {
931
+ manifest.push({
932
+ module: entry.module,
933
+ enabled: entry.enabled
934
+ });
935
+ manifestByName.set(pluginName, manifest[manifest.length - 1]);
936
+ }
937
+ }
938
+ if (extension.defaults) {
939
+ for (const [pluginName, pluginDefaults] of Object.entries(extension.defaults)) {
940
+ defaults[pluginName] = pluginDefaults;
941
+ }
942
+ }
943
+ return {
944
+ manifest,
945
+ defaults
946
+ };
947
+ }
948
+
821
949
  // src/index.ts
822
950
  var Types = {};
823
951
  export {
@@ -826,6 +954,7 @@ export {
826
954
  createPluginInstance,
827
955
  createTestFramework,
828
956
  createVuePlugin,
957
+ extendPreset,
829
958
  validatePreset,
830
959
  validatePresets
831
960
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testforgejs/vue-test-core",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.2",
4
4
  "type": "module",
5
5
  "description": "Core testing utilities and pipeline orchestration for the TestForge component testing framework",
6
6
  "homepage": "https://github.com/testforgejs/testforge#readme",
@@ -51,10 +51,8 @@
51
51
  "vue": "^3.3.0"
52
52
  },
53
53
  "devDependencies": {
54
- "vue-router": "^5.0.0",
55
- "@testforgejs/vue-test-plugin-i18n": "1.0.0-beta.0",
56
- "@testforgejs/vue-test-plugin-pinia": "1.0.0-beta.0",
57
- "@testforgejs/vue-test-plugin-router": "1.0.0-beta.0"
54
+ "@vue/test-utils": "^2.0.0",
55
+ "vue": "^3.3.0"
58
56
  },
59
57
  "tsd": {
60
58
  "directory": "test-d"