@testforgejs/vue-test-core 1.0.0-beta.0 → 1.0.0-beta.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/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,83 @@ 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.
42
54
 
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.
55
+ A preset controls:
44
56
 
45
- TestForge provides an official recommended preset:
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
+ ### Official preset packages
64
+
65
+ TestForge provides a layered preset architecture:
66
+
67
+ ```text
68
+ @testforgejs/vue-test-preset-base
69
+
70
+ ├── @testforgejs/vue-test-preset-recommended
71
+ │ Vitest defaults
72
+
73
+ └── @testforgejs/vue-test-preset-recommended-jest
74
+ Jest defaults
75
+ ```
76
+
77
+ `@testforgejs/vue-test-preset-base` provides shared Vue plugin configuration.
78
+
79
+ Runner-specific recommended presets build on top of the base presets and add configuration required by a particular test runner.
80
+
81
+ For example, the recommended Vitest preset provides `vi.fn` as the Pinia `createSpy` implementation.
82
+
83
+ ### Recommended presets
84
+
85
+ For Vitest:
46
86
 
47
87
  ```bash
48
88
  pnpm add -D @testforgejs/vue-test-preset-recommended
49
89
  ```
50
90
 
51
- The recommended preset provides commonly used integrations such as:
91
+ For Jest:
52
92
 
53
- - Pinia
54
- - Vue Router
55
- - Vue I18n
93
+ ```bash
94
+ pnpm add -D @testforgejs/vue-test-preset-recommended-jest
95
+ ```
96
+
97
+ The recommended presets include configurations for commonly used managed integrations such as:
56
98
 
57
- Additional integrations, such as Vuetify and PrimeVue, are available as separate plugin packages and can be included in your own preset configuration.
99
+ - Pinia;
100
+ - Vue I18n;
101
+ - Vue Router.
102
+
103
+ The recommended preset packages are optional. You can also use the base presets directly or create and compose your own presets.
58
104
 
59
105
  > [!TIP]
60
- > The recommended preset is optional. If your project requires a different set of plugins or configurations, you can create a custom preset.
106
+ > If you are getting started with TestForge, use the recommended preset that matches your test runner.
107
+
108
+ 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
109
 
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.
110
+ ---
63
111
 
64
112
  ## Quick Usage
65
113
 
114
+ The following example uses Vitest and the recommended Vitest preset.
115
+
66
116
  ```typescript
67
- // tests/setup.ts (or any other initialization file in your project)
117
+ // tests/setup.ts
118
+
68
119
  import { createTestFramework } from "@testforgejs/vue-test-core";
69
120
  import { presets } from "@testforgejs/vue-test-preset-recommended";
70
121
 
@@ -78,41 +129,55 @@ export { testComponentFactory };
78
129
  The resulting `testComponentFactory` can then be imported and reused throughout your component tests.
79
130
 
80
131
  ```typescript
81
- // MyComponent.spec.ts (example component test)
132
+ // MyComponent.spec.ts
133
+
134
+ import { describe, expect, it } from "vitest";
135
+
82
136
  import { testComponentFactory } from "@/tests/setup";
83
137
  import MyComponent from "@/components/MyComponent.vue";
84
138
 
85
139
  const factory = testComponentFactory(MyComponent);
86
140
 
87
- test("renders correctly", () => {
88
- const wrapper = factory();
141
+ describe("MyComponent.vue", () => {
142
+ it("renders correctly", () => {
143
+ const wrapper = factory();
89
144
 
90
- expect(wrapper.exists()).toBe(true);
145
+ expect(wrapper.exists()).toBe(true);
146
+ });
91
147
  });
92
148
  ```
93
149
 
150
+ For Jest projects, use `@testforgejs/vue-test-preset-recommended-jest` instead.
151
+
94
152
  👉 For a complete walkthrough, continue with the [Getting Started Guide](https://github.com/testforgejs/testforge/blob/main/docs/getting-started.md).
95
153
 
96
154
  ## Documentation
97
155
 
98
156
  - [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.
157
+ - [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.
158
+ - [Preset Authoring Guide](https://github.com/testforgejs/testforge/blob/main/docs/preset-authoring-guide.md) — Create and compose custom presets.
100
159
  - [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
160
 
103
161
  ## Project
104
162
 
105
163
  This package is part of the **TestForge** monorepo.
106
164
 
107
- - **[TestForge Project Overview](https://github.com/testforgejs/testforge#readme)** — project overview, package ecosystem, roadmap and repository information.
165
+ - **[TestForge Project Overview](https://github.com/testforgejs/testforge#readme)** — Project overview, package ecosystem, roadmap, and repository information.
108
166
  - **Repository:** https://github.com/testforgejs/testforge
109
167
 
110
- ## Related packages
168
+ ## Related Packages
111
169
 
170
+ ### Presets
171
+
172
+ - `@testforgejs/vue-test-preset-base`
112
173
  - `@testforgejs/vue-test-preset-recommended`
113
- - `@testforgejs/vue-test-plugin-router`
174
+ - `@testforgejs/vue-test-preset-recommended-jest`
175
+
176
+ ### Managed Plugins
177
+
114
178
  - `@testforgejs/vue-test-plugin-pinia`
115
179
  - `@testforgejs/vue-test-plugin-i18n`
180
+ - `@testforgejs/vue-test-plugin-router`
116
181
  - `@testforgejs/vue-test-plugin-vuetify`
117
182
  - `@testforgejs/vue-test-plugin-primevue`
118
183
 
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,7 +56,7 @@ 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/presets/validators/validatePreset.ts
59
60
  function validatePreset(name, preset) {
60
61
  if (!preset) {
61
62
  throw new Error(`${ERROR_PREFIX} Preset "${name}" is null or undefined.`);
@@ -91,17 +92,16 @@ function validatePreset(name, preset) {
91
92
  );
92
93
  }
93
94
  const value = preset.defaults[key];
94
- const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
95
- if (!isObject) {
95
+ if (typeof value !== "function") {
96
96
  throw new Error(
97
- `${ERROR_PREFIX} Invalid default configuration for plugin "${key}" in preset "${name}". Expected Object, but received ${typeof value}.`
97
+ `${ERROR_PREFIX} Invalid default configuration for plugin "${key}" in preset "${name}". Expected a plugin options factory function, but received ${typeof value}.`
98
98
  );
99
99
  }
100
100
  });
101
101
  }
102
102
  }
103
103
 
104
- // src/utils/getActivePreset.ts
104
+ // src/presets/getActivePreset.ts
105
105
  function getActivePreset(presets = {}, extraOptions) {
106
106
  const requestedPresetName = extraOptions?.preset?.trim();
107
107
  const activeName = requestedPresetName || DEFAULT_PRESET_NAME;
@@ -416,12 +416,29 @@ var withPluginsManifest = (ctx) => {
416
416
  });
417
417
  };
418
418
 
419
+ // src/presets/utils/buildPluginOptions.ts
420
+ function buildPluginOptions(pluginName, factory) {
421
+ const options = factory();
422
+ if (!isPlainObject(options)) {
423
+ throw new Error(
424
+ `${ERROR_PREFIX} Plugin options factory for "${pluginName}" must return a plain object.`
425
+ );
426
+ }
427
+ return options;
428
+ }
429
+
419
430
  // src/pipeline/middleware/transformers/withPreset.ts
420
431
  var withPreset = (ctx) => {
421
432
  const { preset } = ctx;
422
433
  if (!preset?.defaults) return ctx;
434
+ const pluginDefaultsState = Object.fromEntries(
435
+ Object.entries(preset.defaults).map(([pluginName, factory]) => [
436
+ pluginName,
437
+ buildPluginOptions(pluginName, factory)
438
+ ])
439
+ );
423
440
  return patchResultState(ctx, {
424
- pluginDefaultsState: { ...preset.defaults }
441
+ pluginDefaultsState
425
442
  });
426
443
  };
427
444
 
@@ -625,7 +642,7 @@ function mountWithPlugins(component, ctx, overrides = {}, runtimeOptions = {
625
642
  });
626
643
  }
627
644
 
628
- // src/validators/validatePresets.ts
645
+ // src/presets/validators/validatePresets.ts
629
646
  function validatePresets(presets = {}) {
630
647
  if (!isPlainObject(presets)) {
631
648
  throw new Error(`${ERROR_PREFIX} Presets must be a plain object.`);
@@ -850,6 +867,95 @@ function captureInstance() {
850
867
  };
851
868
  }
852
869
 
870
+ // src/presets/validators/validatePluginDefaults.ts
871
+ function validatePluginDefaults(pluginName, value) {
872
+ if (typeof value !== "function") {
873
+ throw new Error(
874
+ `${ERROR_PREFIX} Invalid defaults for plugin "${pluginName}". Preset defaults must be provided as a plugin options factory function.`
875
+ );
876
+ }
877
+ }
878
+
879
+ // src/presets/validators/validatePresetExtension.ts
880
+ function validatePresetExtension(basePreset, extension) {
881
+ if (!basePreset) {
882
+ throw new Error(`${ERROR_PREFIX} Cannot extend a null or undefined preset.`);
883
+ }
884
+ if (!extension) {
885
+ throw new Error(`${ERROR_PREFIX} Preset extension is null or undefined.`);
886
+ }
887
+ const basePluginNames = new Set(basePreset.manifest.map((entry) => entry.module.getName()));
888
+ const extensionManifest = extension.manifest ?? [];
889
+ const extensionManifestPlugins = /* @__PURE__ */ new Set();
890
+ for (const entry of extensionManifest) {
891
+ const pluginName = entry.module.getName();
892
+ if (extensionManifestPlugins.has(pluginName)) {
893
+ throw new Error(
894
+ `${ERROR_PREFIX} Duplicate plugin "${pluginName}" in preset extension manifest.`
895
+ );
896
+ }
897
+ extensionManifestPlugins.add(pluginName);
898
+ const isNewPlugin = !basePluginNames.has(pluginName);
899
+ if (isNewPlugin) {
900
+ if (entry.enabled === void 0) {
901
+ throw new Error(
902
+ `${ERROR_PREFIX} Cannot add plugin "${pluginName}" to an extended preset without specifying "enabled".`
903
+ );
904
+ }
905
+ const pluginDefaults = extension.defaults?.[pluginName];
906
+ if (pluginDefaults === void 0) {
907
+ throw new Error(
908
+ `${ERROR_PREFIX} Cannot add plugin "${pluginName}" to an extended preset without defining its defaults.`
909
+ );
910
+ }
911
+ }
912
+ }
913
+ if (extension.defaults) {
914
+ for (const [pluginName, pluginDefaults] of Object.entries(extension.defaults)) {
915
+ validatePluginDefaults(pluginName, pluginDefaults);
916
+ const isKnownPlugin = basePluginNames.has(pluginName) || extensionManifestPlugins.has(pluginName);
917
+ if (!isKnownPlugin) {
918
+ throw new Error(
919
+ `${ERROR_PREFIX} Cannot define defaults for unknown plugin "${pluginName}" in a preset extension. The plugin must be declared in the manifest.`
920
+ );
921
+ }
922
+ }
923
+ }
924
+ }
925
+
926
+ // src/presets/extendPreset.ts
927
+ function extendPreset(basePreset, extension) {
928
+ validatePresetExtension(basePreset, extension);
929
+ const manifest = basePreset.manifest.map((entry) => ({ ...entry }));
930
+ const defaults = { ...basePreset.defaults };
931
+ const manifestByName = new Map(manifest.map((entry) => [entry.module.getName(), entry]));
932
+ const extensionManifest = extension.manifest ?? [];
933
+ for (const entry of extensionManifest) {
934
+ const pluginName = entry.module.getName();
935
+ const existingEntry = manifestByName.get(pluginName);
936
+ if (existingEntry) {
937
+ if (entry.enabled !== void 0) {
938
+ existingEntry.enabled = entry.enabled;
939
+ }
940
+ } else {
941
+ manifest.push({
942
+ module: entry.module,
943
+ enabled: entry.enabled
944
+ });
945
+ manifestByName.set(pluginName, manifest[manifest.length - 1]);
946
+ }
947
+ }
948
+ if (extension.defaults) {
949
+ for (const [pluginName, pluginDefaults] of Object.entries(extension.defaults)) {
950
+ defaults[pluginName] = pluginDefaults;
951
+ }
952
+ }
953
+ return {
954
+ manifest,
955
+ defaults
956
+ };
957
+ }
958
+
853
959
  // src/index.ts
854
960
  var Types = {};
855
961
  // Annotate the CommonJS export names for ESM import in node:
@@ -859,6 +965,7 @@ var Types = {};
859
965
  createPluginInstance,
860
966
  createTestFramework,
861
967
  createVuePlugin,
968
+ extendPreset,
862
969
  validatePreset,
863
970
  validatePresets
864
971
  });
package/dist/index.d.cts CHANGED
@@ -29,7 +29,8 @@ 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
+ type PluginConfigDefaults = Record<PluginName, PluginOptionsFactory<RuntimePluginConfig>>;
33
+ type ResolvedPluginDefaults = Record<PluginName, RuntimePluginConfig>;
33
34
  interface PluginRuntimeMeta<TInstance> {
34
35
  __sharedInstance?: TInstance;
35
36
  expose?: (instance: TInstance) => void;
@@ -101,6 +102,11 @@ interface PresetDefinition {
101
102
  defaults: PluginConfigDefaults;
102
103
  }
103
104
  type TestFrameworkPresets = Record<string, PresetDefinition>;
105
+ interface PresetExtension {
106
+ manifest?: PluginManifestEntry<any, any>[];
107
+ defaults?: PluginConfigDefaults;
108
+ }
109
+ type PluginOptionsFactory<TOptions> = () => TOptions;
104
110
  interface PipelineContext {
105
111
  defaultMountOptions: ComponentFactoryOptions;
106
112
  mountOptions: ComponentFactoryOptions;
@@ -119,7 +125,7 @@ type MountOptionsState = Partial<MountingOptions<any, any>>;
119
125
  interface PipelineContextResult {
120
126
  mountOptions: MountOptionsState;
121
127
  global: NonNullable<MountingOptions<any, any>["global"]>;
122
- pluginDefaultsState: PluginConfigDefaults;
128
+ pluginDefaultsState: ResolvedPluginDefaults;
123
129
  plugins: ResolvedPluginOptions;
124
130
  }
125
131
  /**
@@ -185,10 +191,12 @@ declare function createVuePlugin<TPlugin extends Plugin, TOptions extends object
185
191
 
186
192
  declare function captureInstance<T = unknown>(): InstanceCapture<T>;
187
193
 
194
+ declare function extendPreset(basePreset: PresetDefinition, extension: PresetExtension): PresetDefinition;
195
+
188
196
  declare function validatePreset(name: PluginName, preset: PresetDefinition): void;
189
197
 
190
198
  declare function validatePresets(presets?: TestFrameworkPresets): void;
191
199
 
192
200
  declare const Types: {};
193
201
 
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 };
202
+ export { type ComponentFactory, type ComponentFactoryCreator, type ComponentFactoryExtraOptions, type ComponentFactoryOptions, type MountPlugin, type PluginControlOptions, type PluginModule, type PluginOptionsFactory, type PluginOptionsInput, type PluginOptionsMap, type PluginOverridesInput, type PresetDefinition, type TestFramework, type TestFrameworkPresets, Types, captureInstance, createPluginInstance, createTestFramework, createVuePlugin, extendPreset, validatePreset, validatePresets };
package/dist/index.d.ts CHANGED
@@ -29,7 +29,8 @@ 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
+ type PluginConfigDefaults = Record<PluginName, PluginOptionsFactory<RuntimePluginConfig>>;
33
+ type ResolvedPluginDefaults = Record<PluginName, RuntimePluginConfig>;
33
34
  interface PluginRuntimeMeta<TInstance> {
34
35
  __sharedInstance?: TInstance;
35
36
  expose?: (instance: TInstance) => void;
@@ -101,6 +102,11 @@ interface PresetDefinition {
101
102
  defaults: PluginConfigDefaults;
102
103
  }
103
104
  type TestFrameworkPresets = Record<string, PresetDefinition>;
105
+ interface PresetExtension {
106
+ manifest?: PluginManifestEntry<any, any>[];
107
+ defaults?: PluginConfigDefaults;
108
+ }
109
+ type PluginOptionsFactory<TOptions> = () => TOptions;
104
110
  interface PipelineContext {
105
111
  defaultMountOptions: ComponentFactoryOptions;
106
112
  mountOptions: ComponentFactoryOptions;
@@ -119,7 +125,7 @@ type MountOptionsState = Partial<MountingOptions<any, any>>;
119
125
  interface PipelineContextResult {
120
126
  mountOptions: MountOptionsState;
121
127
  global: NonNullable<MountingOptions<any, any>["global"]>;
122
- pluginDefaultsState: PluginConfigDefaults;
128
+ pluginDefaultsState: ResolvedPluginDefaults;
123
129
  plugins: ResolvedPluginOptions;
124
130
  }
125
131
  /**
@@ -185,10 +191,12 @@ declare function createVuePlugin<TPlugin extends Plugin, TOptions extends object
185
191
 
186
192
  declare function captureInstance<T = unknown>(): InstanceCapture<T>;
187
193
 
194
+ declare function extendPreset(basePreset: PresetDefinition, extension: PresetExtension): PresetDefinition;
195
+
188
196
  declare function validatePreset(name: PluginName, preset: PresetDefinition): void;
189
197
 
190
198
  declare function validatePresets(presets?: TestFrameworkPresets): void;
191
199
 
192
200
  declare const Types: {};
193
201
 
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 };
202
+ export { type ComponentFactory, type ComponentFactoryCreator, type ComponentFactoryExtraOptions, type ComponentFactoryOptions, type MountPlugin, type PluginControlOptions, type PluginModule, type PluginOptionsFactory, type PluginOptionsInput, type PluginOptionsMap, type PluginOverridesInput, type PresetDefinition, type TestFramework, type TestFrameworkPresets, Types, captureInstance, createPluginInstance, createTestFramework, createVuePlugin, extendPreset, validatePreset, validatePresets };
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ 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/presets/validators/validatePreset.ts
27
27
  function validatePreset(name, preset) {
28
28
  if (!preset) {
29
29
  throw new Error(`${ERROR_PREFIX} Preset "${name}" is null or undefined.`);
@@ -59,17 +59,16 @@ function validatePreset(name, preset) {
59
59
  );
60
60
  }
61
61
  const value = preset.defaults[key];
62
- const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
63
- if (!isObject) {
62
+ if (typeof value !== "function") {
64
63
  throw new Error(
65
- `${ERROR_PREFIX} Invalid default configuration for plugin "${key}" in preset "${name}". Expected Object, but received ${typeof value}.`
64
+ `${ERROR_PREFIX} Invalid default configuration for plugin "${key}" in preset "${name}". Expected a plugin options factory function, but received ${typeof value}.`
66
65
  );
67
66
  }
68
67
  });
69
68
  }
70
69
  }
71
70
 
72
- // src/utils/getActivePreset.ts
71
+ // src/presets/getActivePreset.ts
73
72
  function getActivePreset(presets = {}, extraOptions) {
74
73
  const requestedPresetName = extraOptions?.preset?.trim();
75
74
  const activeName = requestedPresetName || DEFAULT_PRESET_NAME;
@@ -384,12 +383,29 @@ var withPluginsManifest = (ctx) => {
384
383
  });
385
384
  };
386
385
 
386
+ // src/presets/utils/buildPluginOptions.ts
387
+ function buildPluginOptions(pluginName, factory) {
388
+ const options = factory();
389
+ if (!isPlainObject(options)) {
390
+ throw new Error(
391
+ `${ERROR_PREFIX} Plugin options factory for "${pluginName}" must return a plain object.`
392
+ );
393
+ }
394
+ return options;
395
+ }
396
+
387
397
  // src/pipeline/middleware/transformers/withPreset.ts
388
398
  var withPreset = (ctx) => {
389
399
  const { preset } = ctx;
390
400
  if (!preset?.defaults) return ctx;
401
+ const pluginDefaultsState = Object.fromEntries(
402
+ Object.entries(preset.defaults).map(([pluginName, factory]) => [
403
+ pluginName,
404
+ buildPluginOptions(pluginName, factory)
405
+ ])
406
+ );
391
407
  return patchResultState(ctx, {
392
- pluginDefaultsState: { ...preset.defaults }
408
+ pluginDefaultsState
393
409
  });
394
410
  };
395
411
 
@@ -593,7 +609,7 @@ function mountWithPlugins(component, ctx, overrides = {}, runtimeOptions = {
593
609
  });
594
610
  }
595
611
 
596
- // src/validators/validatePresets.ts
612
+ // src/presets/validators/validatePresets.ts
597
613
  function validatePresets(presets = {}) {
598
614
  if (!isPlainObject(presets)) {
599
615
  throw new Error(`${ERROR_PREFIX} Presets must be a plain object.`);
@@ -818,6 +834,95 @@ function captureInstance() {
818
834
  };
819
835
  }
820
836
 
837
+ // src/presets/validators/validatePluginDefaults.ts
838
+ function validatePluginDefaults(pluginName, value) {
839
+ if (typeof value !== "function") {
840
+ throw new Error(
841
+ `${ERROR_PREFIX} Invalid defaults for plugin "${pluginName}". Preset defaults must be provided as a plugin options factory function.`
842
+ );
843
+ }
844
+ }
845
+
846
+ // src/presets/validators/validatePresetExtension.ts
847
+ function validatePresetExtension(basePreset, extension) {
848
+ if (!basePreset) {
849
+ throw new Error(`${ERROR_PREFIX} Cannot extend a null or undefined preset.`);
850
+ }
851
+ if (!extension) {
852
+ throw new Error(`${ERROR_PREFIX} Preset extension is null or undefined.`);
853
+ }
854
+ const basePluginNames = new Set(basePreset.manifest.map((entry) => entry.module.getName()));
855
+ const extensionManifest = extension.manifest ?? [];
856
+ const extensionManifestPlugins = /* @__PURE__ */ new Set();
857
+ for (const entry of extensionManifest) {
858
+ const pluginName = entry.module.getName();
859
+ if (extensionManifestPlugins.has(pluginName)) {
860
+ throw new Error(
861
+ `${ERROR_PREFIX} Duplicate plugin "${pluginName}" in preset extension manifest.`
862
+ );
863
+ }
864
+ extensionManifestPlugins.add(pluginName);
865
+ const isNewPlugin = !basePluginNames.has(pluginName);
866
+ if (isNewPlugin) {
867
+ if (entry.enabled === void 0) {
868
+ throw new Error(
869
+ `${ERROR_PREFIX} Cannot add plugin "${pluginName}" to an extended preset without specifying "enabled".`
870
+ );
871
+ }
872
+ const pluginDefaults = extension.defaults?.[pluginName];
873
+ if (pluginDefaults === void 0) {
874
+ throw new Error(
875
+ `${ERROR_PREFIX} Cannot add plugin "${pluginName}" to an extended preset without defining its defaults.`
876
+ );
877
+ }
878
+ }
879
+ }
880
+ if (extension.defaults) {
881
+ for (const [pluginName, pluginDefaults] of Object.entries(extension.defaults)) {
882
+ validatePluginDefaults(pluginName, pluginDefaults);
883
+ const isKnownPlugin = basePluginNames.has(pluginName) || extensionManifestPlugins.has(pluginName);
884
+ if (!isKnownPlugin) {
885
+ throw new Error(
886
+ `${ERROR_PREFIX} Cannot define defaults for unknown plugin "${pluginName}" in a preset extension. The plugin must be declared in the manifest.`
887
+ );
888
+ }
889
+ }
890
+ }
891
+ }
892
+
893
+ // src/presets/extendPreset.ts
894
+ function extendPreset(basePreset, extension) {
895
+ validatePresetExtension(basePreset, extension);
896
+ const manifest = basePreset.manifest.map((entry) => ({ ...entry }));
897
+ const defaults = { ...basePreset.defaults };
898
+ const manifestByName = new Map(manifest.map((entry) => [entry.module.getName(), entry]));
899
+ const extensionManifest = extension.manifest ?? [];
900
+ for (const entry of extensionManifest) {
901
+ const pluginName = entry.module.getName();
902
+ const existingEntry = manifestByName.get(pluginName);
903
+ if (existingEntry) {
904
+ if (entry.enabled !== void 0) {
905
+ existingEntry.enabled = entry.enabled;
906
+ }
907
+ } else {
908
+ manifest.push({
909
+ module: entry.module,
910
+ enabled: entry.enabled
911
+ });
912
+ manifestByName.set(pluginName, manifest[manifest.length - 1]);
913
+ }
914
+ }
915
+ if (extension.defaults) {
916
+ for (const [pluginName, pluginDefaults] of Object.entries(extension.defaults)) {
917
+ defaults[pluginName] = pluginDefaults;
918
+ }
919
+ }
920
+ return {
921
+ manifest,
922
+ defaults
923
+ };
924
+ }
925
+
821
926
  // src/index.ts
822
927
  var Types = {};
823
928
  export {
@@ -826,6 +931,7 @@ export {
826
931
  createPluginInstance,
827
932
  createTestFramework,
828
933
  createVuePlugin,
934
+ extendPreset,
829
935
  validatePreset,
830
936
  validatePresets
831
937
  };
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.1",
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,12 @@
51
51
  "vue": "^3.3.0"
52
52
  },
53
53
  "devDependencies": {
54
+ "@vue/test-utils": "^2.0.0",
55
+ "vue": "^3.3.0",
54
56
  "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"
57
+ "@testforgejs/vue-test-plugin-i18n": "1.0.0-beta.1",
58
+ "@testforgejs/vue-test-plugin-router": "1.0.0-beta.1",
59
+ "@testforgejs/vue-test-plugin-pinia": "1.0.0-beta.1"
58
60
  },
59
61
  "tsd": {
60
62
  "directory": "test-d"