@immense/vue-pom-generator 1.0.45 → 1.0.47
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 +15 -0
- package/RELEASE_NOTES.md +30 -33
- package/dist/index.cjs +64 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +64 -2
- package/dist/index.mjs.map +1 -1
- package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
- package/dist/plugin/support/build-plugin.d.ts +1 -0
- package/dist/plugin/support/build-plugin.d.ts.map +1 -1
- package/dist/plugin/support/dev-plugin.d.ts +1 -0
- package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
- package/dist/plugin/support-plugins.d.ts +1 -0
- package/dist/plugin/support-plugins.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +17 -0
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/transform.d.ts +1 -0
- package/dist/transform.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ If you already use Playwright with `getByTestId`, the point is simple: this pack
|
|
|
13
13
|
- **Uses real template signals to name ids and methods.** Click handlers, `v-model`, `id`/`name`, `:to`, wrapper configuration, and a few targeted fallbacks all feed the generated API.
|
|
14
14
|
- **Generates one aggregated TypeScript POM file** plus a stable `index.ts` barrel.
|
|
15
15
|
- **Can generate Playwright fixtures** so tests can request `userListPage` instead of constructing `new UserListPage(page)` manually.
|
|
16
|
+
- **Can fail fast on unnameable wrapper-button actions** so complex inline handlers do not silently degrade into low-signal generated APIs.
|
|
16
17
|
- **Can emit a single C# POM file** for Playwright .NET consumers.
|
|
17
18
|
- **Exposes `virtual:testids`** so your app can import the current collected test-id manifest at runtime.
|
|
18
19
|
- **Ships ESLint rules** to remove legacy manually-authored test ids, ban raw `page` fixture usage in spec callbacks, and discourage raw locator actions on generated getters.
|
|
@@ -73,6 +74,7 @@ The generator does not use one naming trick. It layers several signals.
|
|
|
73
74
|
- **Router links / `:to` bindings** can contribute route-based naming and typed navigation return types when the target can be resolved.
|
|
74
75
|
- **Wrapper components** can be explicit (`nativeWrappers`) or inferred from simple local SFC templates.
|
|
75
76
|
- **Fallback naming exists, but it is intentionally conservative.** That is why `generation.nameCollisionBehavior` exists.
|
|
77
|
+
- **You can opt into stricter wrapper-action generation.** `errorBehavior: "error"` blocks button-like wrapper `:handler` expressions that the generator cannot turn into a semantic action name.
|
|
76
78
|
|
|
77
79
|
Important limit: wrapper inference is helpful, not magical. The current implementation recursively inspects simple local SFC templates for the first inferable primitive (`input`, `textarea`, `select`, `button`, `vselect`, radio/checkbox inputs). It also recognizes some naming patterns like `*Button`. For anything more complex, configure `nativeWrappers` explicitly.
|
|
78
80
|
|
|
@@ -200,6 +202,7 @@ const pomConfig = defineVuePomGeneratorConfig({
|
|
|
200
202
|
script: { defineModel: true, propsDestructure: true },
|
|
201
203
|
},
|
|
202
204
|
logging: { verbosity: "info" },
|
|
205
|
+
errorBehavior: "error",
|
|
203
206
|
injection: {
|
|
204
207
|
attribute: "data-testid",
|
|
205
208
|
viewsDir: "src/views",
|
|
@@ -804,6 +807,18 @@ The sections below follow the actual `VuePomGeneratorPluginOptions` shape from `
|
|
|
804
807
|
logging: { verbosity: "debug" }
|
|
805
808
|
```
|
|
806
809
|
|
|
810
|
+
#### `errorBehavior`
|
|
811
|
+
|
|
812
|
+
- **What it does:** Controls strict/error behavior for generator checks.
|
|
813
|
+
- **Why it exists:** complex inline handlers can otherwise fall through to generic naming, which makes generated APIs harder to discover and review.
|
|
814
|
+
- **Benefit:** `"error"` lets you opt into fail-fast behavior globally, while the object form lets you turn on only the checks you care about.
|
|
815
|
+
- **Without it:** the default is `"ignore"`, so existing permissive fallback behavior remains in place.
|
|
816
|
+
- **Accepted values:**
|
|
817
|
+
- `"ignore"` — keep permissive defaults for all supported checks
|
|
818
|
+
- `"error"` — enable error-on-failure behavior for all supported checks
|
|
819
|
+
- `{ missingSemanticNameBehavior: "error" }` — enable only the button-wrapper semantic-name check
|
|
820
|
+
- **Current scope:** this first pass is intentionally narrow. The object form currently supports `missingSemanticNameBehavior`, which targets button-like wrappers with `:handler`; value/model-driven wrappers still use their existing naming flow.
|
|
821
|
+
|
|
807
822
|
### `injection`
|
|
808
823
|
|
|
809
824
|
`injection` controls compile-time test-id derivation and template rewriting.
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,52 +1,49 @@
|
|
|
1
1
|
● ## Highlights
|
|
2
2
|
|
|
3
|
-
-
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
- Enhanced
|
|
8
|
-
-
|
|
3
|
+
- **Fail-fast validation** for wrapper handlers that cannot be named, improving error detection
|
|
4
|
+
at build time
|
|
5
|
+
- Added comprehensive validation logic across plugin lifecycle (dev and build modes)
|
|
6
|
+
- Expanded test coverage with 54+ new test cases for handler naming scenarios
|
|
7
|
+
- Enhanced error reporting when anonymous or unnameable handlers are detected
|
|
8
|
+
- Updated documentation with new validation requirements and best practices
|
|
9
9
|
|
|
10
10
|
## Changes
|
|
11
11
|
|
|
12
|
-
**
|
|
13
|
-
- Added
|
|
14
|
-
|
|
12
|
+
**Core Validation**
|
|
13
|
+
- Added fail-fast validation in `transform.ts` to detect unnameable wrapper handlers before
|
|
14
|
+
runtime
|
|
15
|
+
- Implemented handler name validation in `create-vue-pom-generator-plugins.ts` with detailed
|
|
16
|
+
error messages
|
|
17
|
+
- Added validation hooks in both `build-plugin.ts` and `dev-plugin.ts` for consistency across
|
|
18
|
+
modes
|
|
15
19
|
|
|
16
|
-
**
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- Minor adjustments to class generation
|
|
20
|
+
**Type System & API**
|
|
21
|
+
- Extended plugin types with new validation options and error handling interfaces
|
|
22
|
+
- Added support for configurable validation behavior in plugin options
|
|
20
23
|
|
|
21
24
|
**Testing**
|
|
22
|
-
- Added
|
|
23
|
-
-
|
|
24
|
-
- Added
|
|
25
|
-
-
|
|
25
|
+
- Added 54 new test cases in `options.test.ts` covering edge cases for handler naming
|
|
26
|
+
- Added 23 new tests in `transform.test.ts` for transformation validation
|
|
27
|
+
- Added 9 coverage tests in `utils-coverage.test.ts`
|
|
28
|
+
- Enhanced `dev-plugin-options.test.ts` with validation scenario tests
|
|
26
29
|
|
|
27
|
-
**Documentation
|
|
28
|
-
- Updated README with
|
|
29
|
-
- Enhanced release workflow with PR release-notes preview comments
|
|
30
|
-
- Workflow improvements for Playwright generator
|
|
30
|
+
**Documentation**
|
|
31
|
+
- Updated README.md with guidance on handler naming requirements and validation behavior
|
|
31
32
|
|
|
32
33
|
## Breaking Changes
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
- **Handler naming validation**: The plugin now throws errors when wrapper handlers cannot be
|
|
36
|
+
named (anonymous functions, computed property names, etc.). Code that previously compiled with
|
|
37
|
+
unnameable handlers will now fail at build time. Ensure all wrapper handlers have statically
|
|
38
|
+
determinable names.
|
|
35
39
|
|
|
36
40
|
## Pull Requests Included
|
|
37
41
|
|
|
38
|
-
- #
|
|
39
|
-
(https://github.com/immense/vue-pom-generator/pull/
|
|
40
|
-
- #6 fix: fail fast on dev snapshot generation errors
|
|
41
|
-
(https://github.com/immense/vue-pom-generator/pull/6)
|
|
42
|
-
- #5 fix: dev-mode POM generation parity with build mode
|
|
43
|
-
(https://github.com/immense/vue-pom-generator/pull/5)
|
|
44
|
-
- #4 Fix keyed POM dedupe and C# navigation returns
|
|
45
|
-
(https://github.com/immense/vue-pom-generator/pull/4)
|
|
46
|
-
- #1 Add PR release-notes preview comments (https://github.com/immense/vue-pom-generator/pull/1)
|
|
42
|
+
- #11: feat: fail fast on unnameable wrapper handlers
|
|
43
|
+
(https://github.com/immense/vue-pom-generator/pull/11) by @dkattan
|
|
47
44
|
|
|
48
45
|
## Testing
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
Comprehensive test coverage added with 80+ new test cases spanning handler naming validation,
|
|
48
|
+
transformation scenarios, plugin options, and edge case coverage.
|
|
52
49
|
|
package/dist/index.cjs
CHANGED
|
@@ -5437,6 +5437,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
5437
5437
|
const existingIdBehavior = options.existingIdBehavior ?? "preserve";
|
|
5438
5438
|
const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
|
|
5439
5439
|
const nameCollisionBehavior = options.nameCollisionBehavior ?? "suffix";
|
|
5440
|
+
const missingSemanticNameBehavior = options.missingSemanticNameBehavior ?? "ignore";
|
|
5440
5441
|
const warn = options.warn;
|
|
5441
5442
|
const vueFilesPathMap = options.vueFilesPathMap;
|
|
5442
5443
|
const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
|
|
@@ -5702,6 +5703,22 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
5702
5703
|
});
|
|
5703
5704
|
};
|
|
5704
5705
|
const { nativeWrappersValue, optionDataTestIdPrefixValue, semanticNameHint } = getNativeWrapperTransformInfo(element, componentName, nativeWrappers);
|
|
5706
|
+
const handlerDirective = element.props.find((p) => {
|
|
5707
|
+
return p.type === compilerCore.NodeTypes.DIRECTIVE && p.name === "bind" && p.arg?.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION && p.arg.content === "handler" && !!p.exp;
|
|
5708
|
+
}) ?? null;
|
|
5709
|
+
const handlerInfo = handlerDirective ? nodeHandlerAttributeInfo(element) : null;
|
|
5710
|
+
if (missingSemanticNameBehavior === "error" && nativeWrappers[element.tag]?.role === "button" && handlerDirective && !handlerInfo) {
|
|
5711
|
+
const loc = element.loc?.start;
|
|
5712
|
+
const locationHint = loc ? `${loc.line}:${loc.column}` : "unknown";
|
|
5713
|
+
const handlerSource = (handlerDirective.exp?.loc?.source ?? "").trim() || "<unknown>";
|
|
5714
|
+
throw new Error(
|
|
5715
|
+
`[vue-pom-generator] Could not derive a semantic POM action name for button-like wrapper in ${componentName} (${context.filename ?? "unknown"}:${locationHint}).
|
|
5716
|
+
Element: <${element.tag}>
|
|
5717
|
+
Handler: ${handlerSource}
|
|
5718
|
+
|
|
5719
|
+
Fix: move complex inline logic into a named function (for example, const onAction = () => ...; then bind :handler="onAction"), or simplify the handler to a direct identifier/call the generator can name. You can also set errorBehavior = "ignore" to keep generic fallback behavior.`
|
|
5720
|
+
);
|
|
5721
|
+
}
|
|
5705
5722
|
if (nativeWrappersValue) {
|
|
5706
5723
|
if (optionDataTestIdPrefixValue) {
|
|
5707
5724
|
const existing = existingIdBehavior === "preserve" ? tryGetExistingElementDataTestId(element, testIdAttribute) : null;
|
|
@@ -5811,7 +5828,6 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
5811
5828
|
});
|
|
5812
5829
|
return;
|
|
5813
5830
|
}
|
|
5814
|
-
const handlerInfo = nodeHandlerAttributeInfo(element);
|
|
5815
5831
|
if (handlerInfo) {
|
|
5816
5832
|
const testId = getHandlerAttributeValueDataTestId(handlerInfo.semanticNameHint);
|
|
5817
5833
|
applyResolvedDataTestIdForElement({
|
|
@@ -5920,6 +5936,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
5920
5936
|
customPomImportNameCollisionBehavior,
|
|
5921
5937
|
testIdAttribute,
|
|
5922
5938
|
nameCollisionBehavior,
|
|
5939
|
+
missingSemanticNameBehavior,
|
|
5923
5940
|
existingIdBehavior,
|
|
5924
5941
|
nativeWrappers,
|
|
5925
5942
|
excludedComponents,
|
|
@@ -6030,6 +6047,7 @@ function createBuildProcessorPlugin(options) {
|
|
|
6030
6047
|
existingIdBehavior: existingIdBehavior ?? "preserve",
|
|
6031
6048
|
testIdAttribute,
|
|
6032
6049
|
nameCollisionBehavior,
|
|
6050
|
+
missingSemanticNameBehavior,
|
|
6033
6051
|
warn: (message) => loggerRef.current.warn(message),
|
|
6034
6052
|
vueFilesPathMap,
|
|
6035
6053
|
wrapperSearchRoots: getWrapperSearchRoots()
|
|
@@ -6154,6 +6172,7 @@ function createDevProcessorPlugin(options) {
|
|
|
6154
6172
|
customPomImportAliases,
|
|
6155
6173
|
customPomImportNameCollisionBehavior,
|
|
6156
6174
|
nameCollisionBehavior = "suffix",
|
|
6175
|
+
missingSemanticNameBehavior,
|
|
6157
6176
|
existingIdBehavior,
|
|
6158
6177
|
testIdAttribute,
|
|
6159
6178
|
routerAwarePoms,
|
|
@@ -6318,6 +6337,7 @@ function createDevProcessorPlugin(options) {
|
|
|
6318
6337
|
{
|
|
6319
6338
|
existingIdBehavior: existingIdBehavior ?? "preserve",
|
|
6320
6339
|
nameCollisionBehavior,
|
|
6340
|
+
missingSemanticNameBehavior,
|
|
6321
6341
|
testIdAttribute,
|
|
6322
6342
|
warn: (message) => loggerRef.current.warn(message),
|
|
6323
6343
|
vueFilesPathMap: targetVuePathMap,
|
|
@@ -6558,6 +6578,7 @@ function createSupportPlugins(options) {
|
|
|
6558
6578
|
scanDirs,
|
|
6559
6579
|
getWrapperSearchRoots,
|
|
6560
6580
|
nameCollisionBehavior = "suffix",
|
|
6581
|
+
missingSemanticNameBehavior,
|
|
6561
6582
|
existingIdBehavior,
|
|
6562
6583
|
outDir,
|
|
6563
6584
|
emitLanguages,
|
|
@@ -6613,6 +6634,7 @@ function createSupportPlugins(options) {
|
|
|
6613
6634
|
customPomImportNameCollisionBehavior,
|
|
6614
6635
|
testIdAttribute,
|
|
6615
6636
|
nameCollisionBehavior,
|
|
6637
|
+
missingSemanticNameBehavior,
|
|
6616
6638
|
existingIdBehavior,
|
|
6617
6639
|
nativeWrappers,
|
|
6618
6640
|
excludedComponents,
|
|
@@ -6641,6 +6663,7 @@ function createSupportPlugins(options) {
|
|
|
6641
6663
|
customPomImportAliases,
|
|
6642
6664
|
customPomImportNameCollisionBehavior,
|
|
6643
6665
|
nameCollisionBehavior,
|
|
6666
|
+
missingSemanticNameBehavior,
|
|
6644
6667
|
existingIdBehavior,
|
|
6645
6668
|
testIdAttribute,
|
|
6646
6669
|
routerAwarePoms,
|
|
@@ -7015,6 +7038,41 @@ function assertNonEmptyStringArray(value, name) {
|
|
|
7015
7038
|
assertNonEmptyString(entry, `${name}[${index}]`);
|
|
7016
7039
|
}
|
|
7017
7040
|
}
|
|
7041
|
+
function assertOneOf(value, allowed, name) {
|
|
7042
|
+
if (!value)
|
|
7043
|
+
return;
|
|
7044
|
+
if (allowed.includes(value)) {
|
|
7045
|
+
return;
|
|
7046
|
+
}
|
|
7047
|
+
throw new TypeError(`${name} must be one of: ${allowed.join(", ")}.`);
|
|
7048
|
+
}
|
|
7049
|
+
function assertErrorBehavior(value, name) {
|
|
7050
|
+
if (!value) {
|
|
7051
|
+
return;
|
|
7052
|
+
}
|
|
7053
|
+
if (value === "ignore" || value === "error") {
|
|
7054
|
+
return;
|
|
7055
|
+
}
|
|
7056
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
7057
|
+
throw new TypeError(`${name} must be "ignore", "error", or an object.`);
|
|
7058
|
+
}
|
|
7059
|
+
const supportedKeys = /* @__PURE__ */ new Set(["missingSemanticNameBehavior"]);
|
|
7060
|
+
for (const key of Object.keys(value)) {
|
|
7061
|
+
if (!supportedKeys.has(key)) {
|
|
7062
|
+
throw new TypeError(`${name} contains unsupported key "${key}".`);
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
assertOneOf(value.missingSemanticNameBehavior, ["ignore", "error"], `${name}.missingSemanticNameBehavior`);
|
|
7066
|
+
}
|
|
7067
|
+
function resolveMissingSemanticNameBehavior(value) {
|
|
7068
|
+
if (!value) {
|
|
7069
|
+
return "ignore";
|
|
7070
|
+
}
|
|
7071
|
+
if (value === "ignore" || value === "error") {
|
|
7072
|
+
return value;
|
|
7073
|
+
}
|
|
7074
|
+
return value.missingSemanticNameBehavior ?? "ignore";
|
|
7075
|
+
}
|
|
7018
7076
|
function assertRouterModuleShims(value, name) {
|
|
7019
7077
|
if (!value)
|
|
7020
7078
|
return;
|
|
@@ -7147,6 +7205,8 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
7147
7205
|
const vuePluginOwnership = isNuxt ? "external" : options.vuePluginOwnership ?? "internal";
|
|
7148
7206
|
const usesExternalVuePlugin = vuePluginOwnership === "external";
|
|
7149
7207
|
const csharp = generationOptions?.csharp;
|
|
7208
|
+
const errorBehavior = options.errorBehavior;
|
|
7209
|
+
const missingSemanticNameBehavior = resolveMissingSemanticNameBehavior(errorBehavior);
|
|
7150
7210
|
const generateFixtures = generationOptions?.playwright?.fixtures;
|
|
7151
7211
|
const customPoms = generationOptions?.playwright?.customPoms;
|
|
7152
7212
|
const resolvedCustomPomAttachments = customPoms?.attachments ?? [];
|
|
@@ -7178,6 +7238,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
7178
7238
|
assertNonEmptyString(testIdAttribute, "[vue-pom-generator] injection.attribute");
|
|
7179
7239
|
assertNonEmptyString(viewsDir, "[vue-pom-generator] injection.viewsDir");
|
|
7180
7240
|
assertNonEmptyStringArray(wrapperSearchRoots, "[vue-pom-generator] injection.wrapperSearchRoots");
|
|
7241
|
+
assertErrorBehavior(errorBehavior, "[vue-pom-generator] errorBehavior");
|
|
7181
7242
|
if (generationEnabled) {
|
|
7182
7243
|
assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
|
|
7183
7244
|
assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
|
|
@@ -7189,7 +7250,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
7189
7250
|
applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompilerOptions, isNuxt ? "nuxt" : vuePluginOwnership);
|
|
7190
7251
|
}
|
|
7191
7252
|
loggerRef.current.info(`projectRoot=${projectRootRef.current}`);
|
|
7192
|
-
loggerRef.current.info(`Active plugins: ${config.plugins.map((p) => p.name).filter((n) => n.includes("vue-pom")).join(", ")}`);
|
|
7253
|
+
loggerRef.current.info(`Active plugins: ${(config.plugins ?? []).map((p) => p.name).filter((n) => n.includes("vue-pom")).join(", ")}`);
|
|
7193
7254
|
}
|
|
7194
7255
|
};
|
|
7195
7256
|
const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, viewsDir);
|
|
@@ -7223,6 +7284,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
7223
7284
|
scanDirs,
|
|
7224
7285
|
getWrapperSearchRoots: getWrapperSearchRootsAbs,
|
|
7225
7286
|
nameCollisionBehavior,
|
|
7287
|
+
missingSemanticNameBehavior,
|
|
7226
7288
|
existingIdBehavior,
|
|
7227
7289
|
outDir,
|
|
7228
7290
|
emitLanguages,
|