@immense/vue-pom-generator 1.0.60 → 1.0.62
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 +9 -1
- package/RELEASE_NOTES.md +22 -28
- package/dist/accessibility-audit.d.ts +20 -0
- package/dist/accessibility-audit.d.ts.map +1 -0
- package/dist/compiler-metadata-utils.d.ts.map +1 -1
- package/dist/index.cjs +185 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +185 -60
- package/dist/index.mjs.map +1 -1
- package/dist/manifest-generator.d.ts +2 -0
- package/dist/manifest-generator.d.ts.map +1 -1
- package/dist/metadata-collector.d.ts +5 -1
- package/dist/metadata-collector.d.ts.map +1 -1
- package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
- package/dist/plugin/resolved-generation-options.d.ts +1 -0
- package/dist/plugin/resolved-generation-options.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +7 -0
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/vue-plugin.d.ts +1 -0
- package/dist/plugin/vue-plugin.d.ts.map +1 -1
- package/dist/tests/accessibility-audit.test.d.ts +2 -0
- package/dist/tests/accessibility-audit.test.d.ts.map +1 -0
- package/dist/tests/resolved-generation-options.test.d.ts +2 -0
- package/dist/tests/resolved-generation-options.test.d.ts.map +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -693,6 +693,7 @@ What it contains:
|
|
|
693
693
|
- `testIdManifest`: each value is a sorted array of collected test ids for that component
|
|
694
694
|
- `pomManifest`: richer per-component metadata including source file, generated locator/property names, and generated action names
|
|
695
695
|
- each manifest entry also carries `locatorDescription`, which matches the human-readable label used by generated Playwright locators
|
|
696
|
+
- each manifest entry may also carry `accessibility`, a compile-time review signal with static metadata, accessible-name hints, and `needsReview`
|
|
696
697
|
|
|
697
698
|
What it is good for:
|
|
698
699
|
|
|
@@ -722,7 +723,7 @@ What it contains:
|
|
|
722
723
|
|
|
723
724
|
- an object keyed by component/page object model class name
|
|
724
725
|
- for each component: source file, whether it is a view or component, sorted test ids, and rich entry metadata
|
|
725
|
-
- for each entry: test id, semantic name, inferred role, generated property name, generated action names,
|
|
726
|
+
- for each entry: test id, semantic name, inferred role, generated property name, generated action names, collected compiler metadata when available, and accessibility review metadata when available
|
|
726
727
|
|
|
727
728
|
## ESLint rules that actually ship
|
|
728
729
|
|
|
@@ -1054,6 +1055,13 @@ Set `generation: false` to keep injection and `virtual:testids` but skip emitted
|
|
|
1054
1055
|
- **Benefit:** you can keep your own BasePage implementation while still using the generator.
|
|
1055
1056
|
- **Without it:** the package uses its bundled `class-generation/BasePage.ts`.
|
|
1056
1057
|
|
|
1058
|
+
#### `generation.accessibilityAudit`
|
|
1059
|
+
|
|
1060
|
+
- **What it does:** emits warnings for generated interactive elements whose accessible-name signals look weak or unverifiable from compile-time metadata.
|
|
1061
|
+
- **Why it exists:** generated POM coverage can look healthy while the underlying control still needs an accessibility review.
|
|
1062
|
+
- **Benefit:** gives you an opt-in review signal without auto-injecting ARIA or guessing runtime behavior.
|
|
1063
|
+
- **Without it:** accessibility review metadata can still appear in `pomManifest`, but no warnings are emitted during compilation.
|
|
1064
|
+
|
|
1057
1065
|
### `generation.router`
|
|
1058
1066
|
|
|
1059
1067
|
If omitted, router introspection is off.
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,33 +1,28 @@
|
|
|
1
|
-
●
|
|
1
|
+
● I'll examine the commits to generate accurate release notes.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
human-readable descriptions for both routes and locators, improving discoverability and
|
|
5
|
-
developer experience
|
|
6
|
-
- **New discoverability module**: Added `pom-discoverability.ts` to centralize description
|
|
7
|
-
generation and introspection logic
|
|
8
|
-
- **Enhanced test coverage**: Expanded tests for class generation, virtual modules, and base
|
|
9
|
-
page functionality
|
|
3
|
+
● Based on the commit details, here are the release notes for v1.0.62:
|
|
10
4
|
|
|
11
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Highlights
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
9
|
+
- Fixed handler name preservation after Vue compiler transformations
|
|
10
|
+
- Improved fallback logic to extract semantic names from author-written source code
|
|
11
|
+
- Enhanced test coverage for handler attribute parsing edge cases
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
- Enhanced `manifest-generator.ts` with description support (+256 lines)
|
|
19
|
-
- Updated method generation to include locator descriptions
|
|
20
|
-
- Improved base page class generation with description metadata
|
|
13
|
+
## Changes
|
|
21
14
|
|
|
22
|
-
**
|
|
23
|
-
-
|
|
24
|
-
-
|
|
15
|
+
**Bug Fixes**
|
|
16
|
+
- Preserve original handler names when Vue compiler rewrites expressions during compilation
|
|
17
|
+
- Extract semantic names from author source (`loc.content`) rather than transformed source when
|
|
18
|
+
they differ
|
|
19
|
+
- Add fallback expression parsing to handle cases where Vue's transformation breaks Babel AST
|
|
20
|
+
parsing
|
|
25
21
|
|
|
26
|
-
**Testing
|
|
27
|
-
- Added
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- Refreshed generated TypeScript fixtures
|
|
22
|
+
**Testing**
|
|
23
|
+
- Added 38 new test cases for handler attribute transformation scenarios
|
|
24
|
+
- Improved coverage for `utils.ts` edge cases with 36 additional tests
|
|
25
|
+
- Fixed missing `SimpleExpressionNode` import in test files
|
|
31
26
|
|
|
32
27
|
## Breaking Changes
|
|
33
28
|
|
|
@@ -35,11 +30,10 @@
|
|
|
35
30
|
|
|
36
31
|
## Pull Requests Included
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
(https://github.com/immense/vue-pom-generator/pull/24)
|
|
33
|
+
None (direct commits to main)
|
|
40
34
|
|
|
41
35
|
## Testing
|
|
42
36
|
|
|
43
|
-
All changes include
|
|
44
|
-
|
|
37
|
+
All changes include comprehensive test coverage. Added 75+ new test cases covering handler
|
|
38
|
+
attribute parsing, Vue compiler transformations, and semantic name extraction edge cases.
|
|
45
39
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ElementMetadata } from "./metadata-collector";
|
|
2
|
+
export interface AccessibilityAuditResult {
|
|
3
|
+
needsReview: boolean;
|
|
4
|
+
accessibleNameSource: "aria-label" | "title" | "text" | "dynamic" | "unknown" | "missing";
|
|
5
|
+
reasons: string[];
|
|
6
|
+
staticAriaLabel?: string;
|
|
7
|
+
staticRole?: string;
|
|
8
|
+
staticTitle?: string;
|
|
9
|
+
staticTextContent?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildAccessibilityAudit(metadata: ElementMetadata | undefined, inferredRole: string | null): AccessibilityAuditResult | undefined;
|
|
12
|
+
export declare function collectAccessibilityReviewWarnings(manifest: Record<string, {
|
|
13
|
+
entries: Array<{
|
|
14
|
+
testId: string;
|
|
15
|
+
generatedPropertyName: string | null;
|
|
16
|
+
inferredRole: string | null;
|
|
17
|
+
accessibility?: AccessibilityAuditResult;
|
|
18
|
+
}>;
|
|
19
|
+
}>): string[];
|
|
20
|
+
//# sourceMappingURL=accessibility-audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility-audit.d.ts","sourceRoot":"","sources":["../accessibility-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,OAAO,CAAC;IACrB,oBAAoB,EAAE,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1F,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAMD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,wBAAwB,GAAG,SAAS,CA4CtC;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;QACxC,MAAM,EAAE,MAAM,CAAC;QACf,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;QACrC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,aAAa,CAAC,EAAE,wBAAwB,CAAC;KAC1C,CAAC,CAAA;CAAE,CAAC,GACJ,MAAM,EAAE,CAmBV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler-metadata-utils.d.ts","sourceRoot":"","sources":["../compiler-metadata-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,oBAAoB,
|
|
1
|
+
{"version":3,"file":"compiler-metadata-utils.d.ts","sourceRoot":"","sources":["../compiler-metadata-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,oBAAoB,EAGrB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,aAAa,GAAG,SAAS,CAAC;AAEvE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,aAAa,GAAE,MAAsB,GAAG,cAAc,CAQ9G;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAcrE;AAED,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CA+C/G;AAkCD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,WAAW,CAAC;IACrB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC,EAAE,OAAO,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,eAAe,GAAG,IAAI,CA2DzB"}
|
package/dist/index.cjs
CHANGED
|
@@ -260,6 +260,7 @@ function resolveGenerationSupportOptions(options) {
|
|
|
260
260
|
nameCollisionBehavior: options.nameCollisionBehavior ?? "error",
|
|
261
261
|
existingIdBehavior: options.existingIdBehavior ?? "error",
|
|
262
262
|
testIdAttribute: (options.testIdAttribute ?? "data-testid").trim() || "data-testid",
|
|
263
|
+
accessibilityAudit: options.accessibilityAudit ?? false,
|
|
263
264
|
routerAwarePoms: options.routerAwarePoms ?? false,
|
|
264
265
|
routerEntry: options.routerEntry,
|
|
265
266
|
routerType: options.routerType ?? "vue-router",
|
|
@@ -1801,11 +1802,12 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1801
1802
|
return null;
|
|
1802
1803
|
}
|
|
1803
1804
|
const exp = handlerDirective.exp;
|
|
1804
|
-
const
|
|
1805
|
-
|
|
1805
|
+
const transformedSource = getVueExpressionSource(exp, "content", "compiled");
|
|
1806
|
+
const authorSource = getVueExpressionSource(exp, "loc", "content", "compiled");
|
|
1807
|
+
if (!authorSource) {
|
|
1806
1808
|
return null;
|
|
1807
1809
|
}
|
|
1808
|
-
const mergeKey = `handler:expr:${
|
|
1810
|
+
const mergeKey = `handler:expr:${authorSource}`;
|
|
1809
1811
|
const expr = tryGetDirectiveBabelAst(handlerDirective, {
|
|
1810
1812
|
preferredViews: ["content", "compiled"],
|
|
1811
1813
|
plugins: ["typescript", "jsx"],
|
|
@@ -1813,9 +1815,7 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
1813
1815
|
// That is fine for Vue codegen, but our semantic-name extraction needs a normal Babel parse tree.
|
|
1814
1816
|
preferExistingAst: false
|
|
1815
1817
|
});
|
|
1816
|
-
|
|
1817
|
-
return null;
|
|
1818
|
-
}
|
|
1818
|
+
const fallbackExpr = transformedSource !== authorSource ? tryParseBabelExpressionFromSource(authorSource, ["typescript", "jsx"]) : null;
|
|
1819
1819
|
const isNodeType2 = (node2, type) => {
|
|
1820
1820
|
return node2 !== null && node2.type === type;
|
|
1821
1821
|
};
|
|
@@ -2051,59 +2051,73 @@ function nodeHandlerAttributeInfo(node) {
|
|
|
2051
2051
|
const limited = parts.slice(0, 2);
|
|
2052
2052
|
return limited.map((p) => `${toPascalCase(p.key)}${p.value}`).join("");
|
|
2053
2053
|
};
|
|
2054
|
-
const
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
const
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2054
|
+
const tryFromCallExpression = (call) => {
|
|
2055
|
+
const resolvedCall = isAwaitExpressionNode(call) ? call.argument : call;
|
|
2056
|
+
if (!isCallExpressionNode(resolvedCall)) {
|
|
2057
|
+
return null;
|
|
2058
|
+
}
|
|
2059
|
+
const name = getLastIdentifierFromMemberChain(resolvedCall.callee);
|
|
2060
|
+
if (!name) {
|
|
2061
|
+
return null;
|
|
2062
|
+
}
|
|
2063
|
+
const suffix = getStableSuffixFromCall(resolvedCall);
|
|
2064
|
+
const semanticNameHint2 = suffix ? `${toPascalCase(name)}${suffix}` : toPascalCase(name);
|
|
2065
|
+
return semanticNameHint2;
|
|
2066
|
+
};
|
|
2067
|
+
const resolveSemanticName = (candidateExpr) => {
|
|
2068
|
+
if (!candidateExpr) {
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
const direct = getLastIdentifierFromMemberChain(candidateExpr);
|
|
2072
|
+
if (direct) {
|
|
2073
|
+
return toPascalCase(direct);
|
|
2074
|
+
}
|
|
2075
|
+
if (isArrowFunctionExpressionNode(candidateExpr)) {
|
|
2076
|
+
const body = candidateExpr.body;
|
|
2077
|
+
const directCall = tryFromCallExpression(body);
|
|
2078
|
+
if (directCall) {
|
|
2079
|
+
return directCall;
|
|
2080
|
+
}
|
|
2081
|
+
if (isAssignmentExpressionNode(body)) {
|
|
2082
|
+
const lhs = getAssignmentTargetName(body.left);
|
|
2083
|
+
if (lhs) {
|
|
2084
|
+
const rhs = stableWordFromValue(body.right);
|
|
2085
|
+
return `Set${toPascalCase(lhs)}${rhs ?? ""}`;
|
|
2086
|
+
}
|
|
2068
2087
|
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
const lhs = getAssignmentTargetName(body.left);
|
|
2079
|
-
if (lhs) {
|
|
2080
|
-
const rhs = stableWordFromValue(body.right);
|
|
2081
|
-
const semanticNameHint = `Set${toPascalCase(lhs)}${rhs ?? ""}`;
|
|
2082
|
-
return { semanticNameHint, mergeKey };
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
if (isBlockStatementNode(body)) {
|
|
2086
|
-
const stmts = body.body ?? [];
|
|
2087
|
-
if (stmts.length > 0) {
|
|
2088
|
-
const firstStmt = stmts[0];
|
|
2089
|
-
if (isReturnStatementNode(firstStmt)) {
|
|
2090
|
-
const fromReturn = tryFromCallExpression(firstStmt.argument ?? null);
|
|
2091
|
-
if (fromReturn) {
|
|
2092
|
-
return { semanticNameHint: fromReturn, mergeKey };
|
|
2088
|
+
if (isBlockStatementNode(body)) {
|
|
2089
|
+
const stmts = body.body ?? [];
|
|
2090
|
+
if (stmts.length > 0) {
|
|
2091
|
+
const firstStmt = stmts[0];
|
|
2092
|
+
if (isReturnStatementNode(firstStmt)) {
|
|
2093
|
+
const fromReturn = tryFromCallExpression(firstStmt.argument ?? null);
|
|
2094
|
+
if (fromReturn) {
|
|
2095
|
+
return fromReturn;
|
|
2096
|
+
}
|
|
2093
2097
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2098
|
+
if (isExpressionStatementNode(firstStmt)) {
|
|
2099
|
+
const fromExpr = tryFromCallExpression(firstStmt.expression ?? null);
|
|
2100
|
+
if (fromExpr) {
|
|
2101
|
+
return fromExpr;
|
|
2102
|
+
}
|
|
2099
2103
|
}
|
|
2100
2104
|
}
|
|
2101
2105
|
}
|
|
2106
|
+
const bodyName = getLastIdentifierFromMemberChain(body);
|
|
2107
|
+
if (bodyName) {
|
|
2108
|
+
return toPascalCase(bodyName);
|
|
2109
|
+
}
|
|
2102
2110
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2111
|
+
return null;
|
|
2112
|
+
};
|
|
2113
|
+
const isVueRefValueAccess = (candidate) => {
|
|
2114
|
+
return isMemberExpressionNode(candidate) && candidate.computed === false && isIdentifierNode2(candidate.property) && candidate.property.name === "value";
|
|
2115
|
+
};
|
|
2116
|
+
const transformedSemanticName = resolveSemanticName(expr);
|
|
2117
|
+
const fallbackSemanticName = resolveSemanticName(fallbackExpr);
|
|
2118
|
+
const semanticNameHint = fallbackSemanticName && transformedSource !== authorSource && isVueRefValueAccess(expr) ? fallbackSemanticName : transformedSemanticName ?? fallbackSemanticName;
|
|
2119
|
+
if (semanticNameHint) {
|
|
2120
|
+
return { semanticNameHint, mergeKey };
|
|
2107
2121
|
}
|
|
2108
2122
|
return null;
|
|
2109
2123
|
}
|
|
@@ -8214,6 +8228,59 @@ function createDevProcessorPlugin(options) {
|
|
|
8214
8228
|
}
|
|
8215
8229
|
};
|
|
8216
8230
|
}
|
|
8231
|
+
function supportsInlineTextAccessibleName(role) {
|
|
8232
|
+
return role === "button" || role === "radio";
|
|
8233
|
+
}
|
|
8234
|
+
function buildAccessibilityAudit(metadata, inferredRole) {
|
|
8235
|
+
if (!metadata || !inferredRole) {
|
|
8236
|
+
return void 0;
|
|
8237
|
+
}
|
|
8238
|
+
const role = normalizePomRoleLabel(inferredRole).toLowerCase();
|
|
8239
|
+
const dynamicProps = new Set(metadata.dynamicProps ?? []);
|
|
8240
|
+
const hasDynamicAccessibleNameSignal = dynamicProps.has("aria-label") || dynamicProps.has("title") || !!metadata.hasDynamicText;
|
|
8241
|
+
let accessibleNameSource;
|
|
8242
|
+
const reasons = [];
|
|
8243
|
+
if (metadata.staticAriaLabel) {
|
|
8244
|
+
accessibleNameSource = "aria-label";
|
|
8245
|
+
} else if (supportsInlineTextAccessibleName(role) && metadata.staticTextContent) {
|
|
8246
|
+
accessibleNameSource = "text";
|
|
8247
|
+
} else if (metadata.staticTitle) {
|
|
8248
|
+
accessibleNameSource = "title";
|
|
8249
|
+
} else if (hasDynamicAccessibleNameSignal) {
|
|
8250
|
+
accessibleNameSource = "dynamic";
|
|
8251
|
+
} else if (role === "input" || role === "select") {
|
|
8252
|
+
accessibleNameSource = "unknown";
|
|
8253
|
+
reasons.push("No inline accessible-name signal was found; the element may rely on external markup such as a separate <label>.");
|
|
8254
|
+
} else {
|
|
8255
|
+
accessibleNameSource = "missing";
|
|
8256
|
+
reasons.push("No compile-time accessible-name signal was found.");
|
|
8257
|
+
}
|
|
8258
|
+
return {
|
|
8259
|
+
needsReview: accessibleNameSource === "unknown" || accessibleNameSource === "missing",
|
|
8260
|
+
accessibleNameSource,
|
|
8261
|
+
reasons,
|
|
8262
|
+
...metadata.staticAriaLabel ? { staticAriaLabel: metadata.staticAriaLabel } : {},
|
|
8263
|
+
...metadata.staticRole ? { staticRole: metadata.staticRole } : {},
|
|
8264
|
+
...metadata.staticTitle ? { staticTitle: metadata.staticTitle } : {},
|
|
8265
|
+
...metadata.staticTextContent ? { staticTextContent: metadata.staticTextContent } : {}
|
|
8266
|
+
};
|
|
8267
|
+
}
|
|
8268
|
+
function collectAccessibilityReviewWarnings(manifest) {
|
|
8269
|
+
const warnings = [];
|
|
8270
|
+
for (const [componentName, component] of Object.entries(manifest)) {
|
|
8271
|
+
for (const entry of component.entries) {
|
|
8272
|
+
if (!entry.accessibility?.needsReview) {
|
|
8273
|
+
continue;
|
|
8274
|
+
}
|
|
8275
|
+
const entryLabel = entry.generatedPropertyName ?? entry.testId;
|
|
8276
|
+
const reasonText = entry.accessibility.reasons.join(" ");
|
|
8277
|
+
warnings.push(
|
|
8278
|
+
`[vue-pom-generator] Accessibility review suggested for ${componentName}.${entryLabel} (role=${entry.inferredRole ?? "unknown"}, testId=${JSON.stringify(entry.testId)}): ${reasonText}`
|
|
8279
|
+
);
|
|
8280
|
+
}
|
|
8281
|
+
}
|
|
8282
|
+
return warnings;
|
|
8283
|
+
}
|
|
8217
8284
|
function removeByKeySegment(value) {
|
|
8218
8285
|
const idx = value.indexOf("ByKey");
|
|
8219
8286
|
if (idx < 0) {
|
|
@@ -8272,6 +8339,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
|
|
|
8272
8339
|
...generatedActionName ? [generatedActionName] : [],
|
|
8273
8340
|
...extraActionNames.filter((name) => name !== generatedActionName)
|
|
8274
8341
|
]));
|
|
8342
|
+
const accessibility = buildAccessibilityAudit(metadata, pom?.nativeRole ?? null);
|
|
8275
8343
|
return {
|
|
8276
8344
|
testId,
|
|
8277
8345
|
selectorPatternKind: entry.selectorValue.patternKind,
|
|
@@ -8282,6 +8350,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
|
|
|
8282
8350
|
nativeRole: pom.nativeRole
|
|
8283
8351
|
}) : componentName,
|
|
8284
8352
|
inferredRole: pom?.nativeRole ?? null,
|
|
8353
|
+
...accessibility ? { accessibility } : {},
|
|
8285
8354
|
generatedPropertyName: pom ? getGeneratedPropertyName(pom) : null,
|
|
8286
8355
|
generatedActionName,
|
|
8287
8356
|
generatedActionNames,
|
|
@@ -8557,6 +8626,33 @@ function parseDynamicProps(dynamicProps) {
|
|
|
8557
8626
|
}
|
|
8558
8627
|
return void 0;
|
|
8559
8628
|
}
|
|
8629
|
+
function findStaticAttributeValue(element, attributeName) {
|
|
8630
|
+
const attribute = element.props.find(
|
|
8631
|
+
(prop) => prop.type === compilerCore.NodeTypes.ATTRIBUTE && prop.name === attributeName
|
|
8632
|
+
);
|
|
8633
|
+
const value = attribute?.value?.content?.trim();
|
|
8634
|
+
return value ? value : void 0;
|
|
8635
|
+
}
|
|
8636
|
+
function collectStaticTextContent(children) {
|
|
8637
|
+
const parts = [];
|
|
8638
|
+
const visit = (nodes) => {
|
|
8639
|
+
for (const node of nodes) {
|
|
8640
|
+
if (node.type === compilerCore.NodeTypes.TEXT) {
|
|
8641
|
+
const text2 = node.content.replace(/\s+/g, " ").trim();
|
|
8642
|
+
if (text2) {
|
|
8643
|
+
parts.push(text2);
|
|
8644
|
+
}
|
|
8645
|
+
continue;
|
|
8646
|
+
}
|
|
8647
|
+
if (node.type === compilerCore.NodeTypes.ELEMENT) {
|
|
8648
|
+
visit(node.children);
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
};
|
|
8652
|
+
visit(children);
|
|
8653
|
+
const text = parts.join(" ").replace(/\s+/g, " ").trim();
|
|
8654
|
+
return text || void 0;
|
|
8655
|
+
}
|
|
8560
8656
|
function tryCreateElementMetadata(args) {
|
|
8561
8657
|
const { element, semanticNameMap } = args;
|
|
8562
8658
|
const testIdAttribute = (args.testIdAttribute ?? "data-testid").trim() || "data-testid";
|
|
@@ -8580,13 +8676,9 @@ function tryCreateElementMetadata(args) {
|
|
|
8580
8676
|
dynamicPropsList = [content];
|
|
8581
8677
|
}
|
|
8582
8678
|
}
|
|
8583
|
-
const semanticName = semanticNameMap.get(testId);
|
|
8584
|
-
if (!semanticName) {
|
|
8585
|
-
return null;
|
|
8586
|
-
}
|
|
8587
8679
|
const metadata = {
|
|
8588
8680
|
testId,
|
|
8589
|
-
semanticName,
|
|
8681
|
+
semanticName: semanticNameMap.get(testId),
|
|
8590
8682
|
tag: element.tag,
|
|
8591
8683
|
tagType: element.tagType,
|
|
8592
8684
|
patchFlag,
|
|
@@ -8595,7 +8687,11 @@ function tryCreateElementMetadata(args) {
|
|
|
8595
8687
|
hasClickHandler: patchFlag ? Boolean(patchFlag & 32) : void 0,
|
|
8596
8688
|
hasDynamicClass: patchFlag ? Boolean(patchFlag & 2) : void 0,
|
|
8597
8689
|
hasDynamicStyle: patchFlag ? Boolean(patchFlag & 4) : void 0,
|
|
8598
|
-
hasDynamicText: patchFlag ? Boolean(patchFlag & 1) : void 0
|
|
8690
|
+
hasDynamicText: patchFlag ? Boolean(patchFlag & 1) : void 0,
|
|
8691
|
+
staticAriaLabel: findStaticAttributeValue(element, "aria-label"),
|
|
8692
|
+
staticRole: findStaticAttributeValue(element, "role"),
|
|
8693
|
+
staticTitle: findStaticAttributeValue(element, "title"),
|
|
8694
|
+
staticTextContent: collectStaticTextContent(element.children)
|
|
8599
8695
|
};
|
|
8600
8696
|
return metadata;
|
|
8601
8697
|
}
|
|
@@ -8647,6 +8743,7 @@ function extractMetadataAfterTransform(ast, componentName, elementMetadata, sema
|
|
|
8647
8743
|
if (componentMetadata.size > 0) {
|
|
8648
8744
|
elementMetadata.set(componentName, componentMetadata);
|
|
8649
8745
|
}
|
|
8746
|
+
return componentMetadata;
|
|
8650
8747
|
}
|
|
8651
8748
|
function createVuePluginWithTestIds(options) {
|
|
8652
8749
|
const {
|
|
@@ -8661,11 +8758,13 @@ function createVuePluginWithTestIds(options) {
|
|
|
8661
8758
|
excludedComponents,
|
|
8662
8759
|
getViewsDirAbs,
|
|
8663
8760
|
testIdAttribute,
|
|
8761
|
+
accessibilityAudit,
|
|
8664
8762
|
loggerRef,
|
|
8665
8763
|
getSourceDirs,
|
|
8666
8764
|
getWrapperSearchRoots,
|
|
8667
8765
|
getProjectRoot
|
|
8668
8766
|
} = options;
|
|
8767
|
+
const lastAccessibilityWarningSignatureByComponent = /* @__PURE__ */ new Map();
|
|
8669
8768
|
const getComponentNameFromPath = (filename) => {
|
|
8670
8769
|
return resolveComponentNameFromPath({
|
|
8671
8770
|
filename,
|
|
@@ -8738,13 +8837,37 @@ function createVuePluginWithTestIds(options) {
|
|
|
8738
8837
|
)
|
|
8739
8838
|
);
|
|
8740
8839
|
return () => {
|
|
8741
|
-
extractMetadataAfterTransform(
|
|
8840
|
+
const componentMetadata = extractMetadataAfterTransform(
|
|
8742
8841
|
node,
|
|
8743
8842
|
componentName,
|
|
8744
8843
|
elementMetadata,
|
|
8745
8844
|
semanticNameMap,
|
|
8746
8845
|
testIdAttribute
|
|
8747
8846
|
);
|
|
8847
|
+
if (!accessibilityAudit) {
|
|
8848
|
+
return;
|
|
8849
|
+
}
|
|
8850
|
+
const dependencies = componentHierarchyMap.get(componentName);
|
|
8851
|
+
if (!dependencies || componentMetadata.size === 0) {
|
|
8852
|
+
return;
|
|
8853
|
+
}
|
|
8854
|
+
const manifest = buildPomManifest(
|
|
8855
|
+
/* @__PURE__ */ new Map([[componentName, dependencies]]),
|
|
8856
|
+
/* @__PURE__ */ new Map([[componentName, componentMetadata]])
|
|
8857
|
+
);
|
|
8858
|
+
const warnings = collectAccessibilityReviewWarnings(manifest);
|
|
8859
|
+
const signature = warnings.join("\n");
|
|
8860
|
+
if (!signature) {
|
|
8861
|
+
lastAccessibilityWarningSignatureByComponent.delete(componentName);
|
|
8862
|
+
return;
|
|
8863
|
+
}
|
|
8864
|
+
if (lastAccessibilityWarningSignatureByComponent.get(componentName) === signature) {
|
|
8865
|
+
return;
|
|
8866
|
+
}
|
|
8867
|
+
lastAccessibilityWarningSignatureByComponent.set(componentName, signature);
|
|
8868
|
+
for (const warning of warnings) {
|
|
8869
|
+
loggerRef.current.warn(warning);
|
|
8870
|
+
}
|
|
8748
8871
|
};
|
|
8749
8872
|
}
|
|
8750
8873
|
let transform = perFileTransform.get(componentName);
|
|
@@ -9109,6 +9232,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
9109
9232
|
nameCollisionBehavior: generationOptions?.nameCollisionBehavior,
|
|
9110
9233
|
existingIdBehavior: resolvedInjectionOptions.existingIdBehavior,
|
|
9111
9234
|
testIdAttribute,
|
|
9235
|
+
accessibilityAudit: generationOptions?.accessibilityAudit,
|
|
9112
9236
|
routerAwarePoms: typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt",
|
|
9113
9237
|
routerEntry,
|
|
9114
9238
|
routerType,
|
|
@@ -9202,6 +9326,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
9202
9326
|
excludedComponents,
|
|
9203
9327
|
getViewsDirAbs,
|
|
9204
9328
|
testIdAttribute,
|
|
9329
|
+
accessibilityAudit: resolvedGenerationOptions.accessibilityAudit,
|
|
9205
9330
|
loggerRef,
|
|
9206
9331
|
getSourceDirs,
|
|
9207
9332
|
getWrapperSearchRoots: getWrapperSearchRootsAbs,
|