@immense/vue-pom-generator 1.0.52 → 1.0.54

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.
Files changed (51) hide show
  1. package/README.md +71 -27
  2. package/RELEASE_NOTES.md +38 -27
  3. package/class-generation/base-page.ts +18 -6
  4. package/class-generation/callout.ts +229 -679
  5. package/class-generation/floating-ui-callout.ts +857 -0
  6. package/class-generation/index.ts +24 -15
  7. package/class-generation/pointer.ts +152 -109
  8. package/dist/class-generation/base-page.d.ts +11 -5
  9. package/dist/class-generation/base-page.d.ts.map +1 -1
  10. package/dist/class-generation/callout.d.ts +44 -1
  11. package/dist/class-generation/callout.d.ts.map +1 -1
  12. package/dist/class-generation/floating-ui-callout.d.ts +4 -0
  13. package/dist/class-generation/floating-ui-callout.d.ts.map +1 -0
  14. package/dist/class-generation/index.d.ts +3 -2
  15. package/dist/class-generation/index.d.ts.map +1 -1
  16. package/dist/class-generation/pointer.d.ts +24 -5
  17. package/dist/class-generation/pointer.d.ts.map +1 -1
  18. package/dist/index.cjs +783 -231
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.mjs +783 -231
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/plugin/create-vue-pom-generator-plugins.d.ts +2 -2
  25. package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
  26. package/dist/plugin/nuxt-discovery.d.ts +47 -0
  27. package/dist/plugin/nuxt-discovery.d.ts.map +1 -0
  28. package/dist/plugin/path-utils.d.ts +4 -5
  29. package/dist/plugin/path-utils.d.ts.map +1 -1
  30. package/dist/plugin/support/build-plugin.d.ts +6 -3
  31. package/dist/plugin/support/build-plugin.d.ts.map +1 -1
  32. package/dist/plugin/support/dev-plugin.d.ts +6 -3
  33. package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
  34. package/dist/plugin/support/virtual-modules.d.ts +2 -2
  35. package/dist/plugin/support/virtual-modules.d.ts.map +1 -1
  36. package/dist/plugin/support-plugins.d.ts +5 -2
  37. package/dist/plugin/support-plugins.d.ts.map +1 -1
  38. package/dist/plugin/types.d.ts +35 -19
  39. package/dist/plugin/types.d.ts.map +1 -1
  40. package/dist/plugin/vue-plugin.d.ts +1 -1
  41. package/dist/plugin/vue-plugin.d.ts.map +1 -1
  42. package/dist/router-introspection.d.ts +4 -2
  43. package/dist/router-introspection.d.ts.map +1 -1
  44. package/dist/tests/nuxt-discovery.test.d.ts +2 -0
  45. package/dist/tests/nuxt-discovery.test.d.ts.map +1 -0
  46. package/dist/vite.config.d.ts.map +1 -1
  47. package/package.json +9 -13
  48. package/dist/plugin/support/generation-metrics.d.ts +0 -10
  49. package/dist/plugin/support/generation-metrics.d.ts.map +0 -1
  50. package/dist/tests/generation-metrics.test.d.ts +0 -2
  51. package/dist/tests/generation-metrics.test.d.ts.map +0 -1
package/README.md CHANGED
@@ -74,7 +74,7 @@ The generator does not use one naming trick. It layers several signals.
74
74
  - **Router links / `:to` bindings** can contribute route-based naming and typed navigation return types when the target can be resolved.
75
75
  - **Wrapper components** can be explicit (`nativeWrappers`) or inferred from simple local SFC templates.
76
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.
77
+ - **Wrapper-action generation fails fast by default.** The generator blocks button-like wrapper `:handler` expressions that it cannot turn into a semantic action name; set `errorBehavior: "ignore"` if you explicitly want the old permissive fallback.
78
78
 
79
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.
80
80
 
@@ -191,9 +191,10 @@ Exports:
191
191
  - `createVuePomGeneratorPlugins()`
192
192
  - `vuePomGenerator()` (alias)
193
193
  - `defineVuePomGeneratorConfig()`
194
+ - `defineNuxtPomGeneratorConfig()`
194
195
  - `@immense/vue-pom-generator/eslint`
195
196
 
196
- ## Basic Vite setup
197
+ ## Basic Vue/Vite setup
197
198
 
198
199
  ```ts
199
200
  import { defineConfig } from "vite";
@@ -208,7 +209,8 @@ const pomConfig = defineVuePomGeneratorConfig({
208
209
  injection: {
209
210
  attribute: "data-testid",
210
211
  viewsDir: "src/views",
211
- scanDirs: ["src"],
212
+ componentDirs: ["src/components"],
213
+ layoutDirs: ["src/layouts"],
212
214
  wrapperSearchRoots: ["../shared-ui/src/components"],
213
215
  nativeWrappers: {
214
216
  AppButton: { role: "button" },
@@ -261,6 +263,29 @@ export default defineConfig({
261
263
  });
262
264
  ```
263
265
 
266
+ ## Basic Nuxt setup
267
+
268
+ ```ts
269
+ import { defineNuxtConfig } from "nuxt/config";
270
+ import { defineNuxtPomGeneratorConfig, vuePomGenerator } from "@immense/vue-pom-generator";
271
+
272
+ const pomConfig = defineNuxtPomGeneratorConfig({
273
+ generation: {
274
+ outDir: "tests/playwright/__generated__",
275
+ },
276
+ });
277
+
278
+ export default defineNuxtConfig({
279
+ vite: {
280
+ plugins: [
281
+ ...vuePomGenerator(pomConfig),
282
+ ],
283
+ },
284
+ });
285
+ ```
286
+
287
+ Nuxt projects are auto-detected from standard project artifacts, and their pages/layouts/components are resolved from Nuxt's own config. That means custom page directories such as `dir.pages = "views"` come from `nuxt.config`, while component directories are picked up automatically from Nuxt conventions/config. `defineNuxtPomGeneratorConfig(...)` is optional, but it keeps the config surface Nuxt-specific at type-check time.
288
+
264
289
  ### Important Vite ownership rule
265
290
 
266
291
  By default, this package creates and returns its own `@vitejs/plugin-vue` instance.
@@ -289,7 +314,7 @@ export default defineConfig({
289
314
  });
290
315
  ```
291
316
 
292
- Nuxt-style routing also uses the resolved app-owned Vue plugin. In practice, if you use `generation.router.type: "nuxt"`, think in terms of external Vue plugin ownership.
317
+ Nuxt-style routing also uses the resolved app-owned Vue plugin. In practice, Nuxt projects are auto-detected and use external Vue plugin ownership automatically.
293
318
 
294
319
  ## What gets generated
295
320
 
@@ -320,7 +345,7 @@ If you emit outside a `__generated__` path, the generator also manages `.gitattr
320
345
 
321
346
  This is important if you are deciding whether the tool will fit into a real codebase.
322
347
 
323
- - **Dev server:** on startup, it scans the configured `scanDirs`, compiles each `.vue` file into a snapshot, writes the configured TypeScript outputs once, then batches add/change/delete events and regenerates incrementally.
348
+ - **Dev server:** on startup, it scans the configured Vue page/component/layout directories (or the directories resolved from Nuxt config in Nuxt mode), compiles each `.vue` file into a snapshot, writes the configured TypeScript outputs once, then batches add/change/delete events and regenerates incrementally.
324
349
  - **Build:** it generates from the richest build pass it sees, which matters because Vite can run multiple passes (for example SSR plus client). The generator avoids letting a thinner pass clobber a richer one.
325
350
  - **Always-on virtual module:** `virtual:testids` is registered whether generation is enabled or disabled.
326
351
  - **Generation can be disabled:** `generation: false` still keeps compile-time test-id injection and the virtual module, but skips emitted POM files.
@@ -592,6 +617,7 @@ What it gives you:
592
617
  - lower-camel-case fixtures for component classes too
593
618
  - `pomFactory.create(Ctor)` for ad-hoc page-object construction inside tests
594
619
  - an `animation` option that wires the generated runtime's pointer settings
620
+ - per-page `renderers` overrides so you can keep the simple default callout or swap in a custom pointer / callout overlay implementation such as the bundled `floating-ui-callout.ts` renderer
595
621
 
596
622
  Current caveats:
597
623
 
@@ -600,6 +626,12 @@ Current caveats:
600
626
  - component fixtures are skipped when their lower-camel-case name would collide with reserved Playwright fixture names such as `page`, `context`, `browser`, or `request`
601
627
  - an override class still needs a `new (page)`-compatible constructor because that is what fixtures call
602
628
 
629
+ By default the runtime uses a simple red fallback bubble. The example below uses the optional floating-ui renderer so the callout can auto-place around nearby UI and point back to the target with an arrow.
630
+
631
+ Example floating-ui callout sequence captured from the Playwright fixture coverage:
632
+
633
+ ![Pointer callout sequence](./docs/assets/pointer-callout-sequence.gif)
634
+
603
635
  ## TypeScript vs C# output
604
636
 
605
637
  ### TypeScript output
@@ -821,12 +853,12 @@ The sections below follow the actual `VuePomGeneratorPluginOptions` shape from `
821
853
 
822
854
  - **What it does:** Controls strict/error behavior for generator checks.
823
855
  - **Why it exists:** complex inline handlers can otherwise fall through to generic naming, which makes generated APIs harder to discover and review.
824
- - **Benefit:** `"error"` lets you opt into fail-fast behavior globally, while the object form lets you turn on only the checks you care about.
825
- - **Without it:** the default is `"ignore"`, so existing permissive fallback behavior remains in place.
856
+ - **Benefit:** fail-fast behavior is the default, while `"ignore"` or the object form let you opt back into only the permissive checks you want.
857
+ - **Without it:** the default is `"error"`, so unsupported button-wrapper handlers stop generation instead of silently falling back.
826
858
  - **Accepted values:**
827
859
  - `"ignore"` — keep permissive defaults for all supported checks
828
- - `"error"` — enable error-on-failure behavior for all supported checks
829
- - `{ missingSemanticNameBehavior: "error" }` — enable only the button-wrapper semantic-name check
860
+ - `"error"` — enable error-on-failure behavior for all supported checks (default)
861
+ - `{ missingSemanticNameBehavior: "ignore" }` — opt out only of the button-wrapper semantic-name check
830
862
  - **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.
831
863
 
832
864
  ### `injection`
@@ -857,6 +889,30 @@ The sections below follow the actual `VuePomGeneratorPluginOptions` shape from `
857
889
  injection: { viewsDir: "app/pages" }
858
890
  ```
859
891
 
892
+ #### `injection.componentDirs`
893
+
894
+ - **What it does:** Lists the directories that should be treated as reusable component roots.
895
+ - **Why it exists:** Vue apps often keep components outside a single catch-all tree, and the generator needs explicit component roots now that generic scan lists are gone.
896
+ - **Benefit:** component naming and filesystem supplementation stay aligned with the directories you actually consider components.
897
+ - **Without it:** the generator uses `["src/components"]`.
898
+ - **Example:**
899
+
900
+ ```ts
901
+ injection: { componentDirs: ["src/components", "../shared-ui/src/components"] }
902
+ ```
903
+
904
+ #### `injection.layoutDirs`
905
+
906
+ - **What it does:** Lists layout-style Vue directories that should be included in generation and naming.
907
+ - **Why it exists:** some Vue apps keep shell components outside `views` and `components`, but still want them in the generated POM graph.
908
+ - **Benefit:** layouts participate explicitly without falling back to a generic scan of unrelated folders.
909
+ - **Without it:** the generator uses `["src/layouts"]`.
910
+ - **Example:**
911
+
912
+ ```ts
913
+ injection: { layoutDirs: ["src/layouts"] }
914
+ ```
915
+
860
916
  #### `injection.nativeWrappers`
861
917
 
862
918
  - **What it does:** Describes wrapper components so the generator can treat them like native controls.
@@ -910,24 +966,12 @@ The sections below follow the actual `VuePomGeneratorPluginOptions` shape from `
910
966
  injection: { excludeComponents: ["LegacyWidget"] }
911
967
  ```
912
968
 
913
- #### `injection.scanDirs`
914
-
915
- - **What it does:** Sets which directories are scanned for `.vue` files when building the POM graph.
916
- - **Why it exists:** real projects rarely keep all SFCs under one folder, especially in Nuxt or monorepos.
917
- - **Benefit:** generation sees the same files your app actually uses.
918
- - **Without it:** the generator scans `src`.
919
- - **Example:**
920
-
921
- ```ts
922
- injection: { scanDirs: ["src", "components", "layouts"] }
923
- ```
924
-
925
969
  #### `injection.wrapperSearchRoots`
926
970
 
927
- - **What it does:** Adds extra roots for wrapper inference outside `scanDirs`.
971
+ - **What it does:** Adds extra roots for wrapper inference outside the configured page/component/layout directories.
928
972
  - **Why it exists:** wrapper components often live in sibling packages or shared UI workspaces.
929
973
  - **Benefit:** local wrapper inference can still work across package boundaries.
930
- - **Without it:** no extra wrapper lookup is done outside the scanned app directories.
974
+ - **Without it:** no extra wrapper lookup is done outside the configured app directories.
931
975
  - **Example:**
932
976
 
933
977
  ```ts
@@ -1013,13 +1057,13 @@ If omitted, router introspection is off.
1013
1057
 
1014
1058
  #### `generation.router.type`
1015
1059
 
1016
- - **What it does:** Chooses the router discovery strategy.
1017
- - **Why it exists:** standard Vue-router apps and Nuxt file-based routing need different discovery paths.
1018
- - **Benefit:** one config field covers both app styles.
1060
+ - **What it does:** Chooses the Vue-router discovery strategy for standard Vue apps.
1061
+ - **Why it exists:** some Vue apps want router introspection without any Nuxt integration.
1062
+ - **Benefit:** keeps Vue-router discovery explicit.
1019
1063
  - **Without it:** defaults to `"vue-router"`.
1064
+ - **Nuxt note:** Nuxt projects are auto-detected. Do not set `generation.router.type: "nuxt"`.
1020
1065
  - **Current options:**
1021
1066
  - `"vue-router"`
1022
- - `"nuxt"`
1023
1067
 
1024
1068
  #### `generation.router.moduleShims`
1025
1069
 
package/RELEASE_NOTES.md CHANGED
@@ -1,43 +1,54 @@
1
- # Release Notes: v1.0.52
2
-
3
- ## Highlights
4
-
5
- - Relaxed Vite peer dependency constraint to support newer Vite versions
6
- - Improved AST-based handling of nullish coalescing in key expressions
7
- - Fixed repository-wide lint failures and updated lint configuration
8
- - Enhanced test coverage for utility functions and transform edge cases
1
+ ## Highlights
2
+
3
+ - **Zero runtime dependencies**: Eliminated all runtime dependencies for smaller bundle size and
4
+ reduced version conflicts
5
+ - **Pluggable callout renderer architecture**: Extracted floating-ui implementation into
6
+ swappable renderer system
7
+ - **Stricter semantic naming**: Unnameable wrapper handlers now fail fast by default instead of
8
+ silently succeeding
9
+ - **Streamlined callout placement pipeline**: Refactored pointer callout rendering for improved
10
+ maintainability
11
+ - **Removed peer dependencies**: No longer requires `vue`, `vitest`, or `@vue/shared` as peer
12
+ dependencies
9
13
 
10
14
  ## Changes
11
15
 
12
- **Dependencies**
13
- - Relaxed Vite peer dependency version range to improve compatibility with downstream projects
16
+ ### Dependencies
17
+ - Eliminated all runtime dependencies
18
+ ([#9](https://github.com/immense/vue-pom-generator/pull/9))
19
+ - Removed `vue`, `vitest`, and `@vue/shared` peer dependencies
20
+ - Updated package structure for zero external runtime requirements
14
21
 
15
- **Code Quality & Linting**
16
- - Added ESLint rule exceptions for generated runtime code patterns
17
- - Clarified AST and lint rule interactions in configuration
18
- - Polished lint cleanup helper utilities
19
- - Fixed existing lint violations across the codebase
22
+ ### Callout Rendering
23
+ - Extracted floating-ui callout renderer into separate module (`floating-ui-callout.ts`)
24
+ - Introduced pluggable pointer callout renderer system
25
+ - Streamlined callout placement pipeline for better performance
26
+ - Refactored pointer rendering logic for extensibility
20
27
 
21
- **AST & Transform Logic**
22
- - Implemented AST-based splitting for nullish coalescing key expressions
23
- - Updated transform logic to handle edge cases more robustly
24
- - Improved runtime generation to meet updated lint expectations
28
+ ### Error Handling
29
+ - Semantic-name validation failures now default to throwing errors instead of warnings
30
+ - Improved fail-fast behavior for unnameable wrapper handlers
25
31
 
26
- **Testing**
27
- - Added test coverage for new utility functions
28
- - Updated test expectations for class generation coverage
29
- - Enhanced transform tests with additional edge case scenarios
32
+ ### Testing
33
+ - Aligned dev plugin options across test suite
34
+ - Added pointer callout sequence documentation (GIF)
35
+ - Enhanced test coverage for pointer rendering
30
36
 
31
37
  ## Breaking Changes
32
38
 
33
- None.
39
+ - **Peer dependencies removed**: `vue`, `vitest`, and `@vue/shared` are no longer peer
40
+ dependencies. Ensure your build still works if you relied on these being present.
41
+ - **Semantic naming errors**: Invalid semantic names now throw by default instead of warning.
42
+ Handlers that cannot be named semantically will cause build failures unless explicitly
43
+ configured otherwise.
34
44
 
35
45
  ## Pull Requests Included
36
46
 
37
- - #13 Relax Vite peer dependency range (https://github.com/immense/vue-pom-generator/pull/13)
47
+ - [#9](https://github.com/immense/vue-pom-generator/pull/9) - refactor(deps): eliminate all
48
+ runtime dependencies
38
49
 
39
50
  ## Testing
40
51
 
41
- All changes validated through existing test suite (`npm test`). Added test coverage for new
42
- utility functions and transform edge cases.
52
+ Comprehensive test updates included to validate dependency removal, plugin option alignment, and
53
+ pointer callout rendering behavior.
43
54
 
@@ -2,8 +2,8 @@ import type { PwLocator, PwPage } from "./playwright-types";
2
2
  import { TESTID_CLICK_EVENT_NAME, TESTID_CLICK_EVENT_STRICT_FLAG } from "../click-instrumentation";
3
3
  import type { TestIdClickEventDetail } from "../click-instrumentation";
4
4
  import { Callout } from "./callout";
5
- import { Pointer } from "./pointer";
6
- import type { AfterPointerClick, AfterPointerClickInfo } from "./pointer";
5
+ import type { CalloutRenderer } from "./callout";
6
+ import { Pointer, type AfterPointerClick, type AfterPointerClickInfo, type PointerRenderer } from "./pointer";
7
7
 
8
8
  // Click instrumentation is optional for generated POMs.
9
9
  //
@@ -55,6 +55,14 @@ export type Fluent<T extends object> = DeepFluent<T, T> & PromiseLike<T>;
55
55
 
56
56
  export type ValueFluent<T> = DeepValueFluent<T> & PromiseLike<T>;
57
57
 
58
+ export interface BasePageOptions {
59
+ renderers?: {
60
+ callout?: CalloutRenderer;
61
+ pointer?: PointerRenderer;
62
+ };
63
+ testIdAttribute?: string;
64
+ }
65
+
58
66
  export class ObjectId {
59
67
  private readonly raw: string;
60
68
 
@@ -99,11 +107,15 @@ export class BasePage {
99
107
  /**
100
108
  * @param {Page} page - Playwright page object
101
109
  */
102
- constructor(protected page: PwPage, options?: { testIdAttribute?: string }) {
110
+ public constructor(protected page: PwPage, options?: BasePageOptions) {
103
111
  this.testIdAttribute = (options?.testIdAttribute || "data-testid").trim() || "data-testid";
104
112
 
105
- this.callout = new Callout(this.page);
106
- this.pointer = new Pointer(this.page, this.testIdAttribute, this.callout);
113
+ const pointerRenderer = options?.renderers?.pointer;
114
+ this.callout = new Callout(this.page, {
115
+ extraOverlayIds: pointerRenderer?.overlayIds,
116
+ renderer: options?.renderers?.callout,
117
+ });
118
+ this.pointer = new Pointer(this.page, this.testIdAttribute, this.callout, pointerRenderer);
107
119
  }
108
120
 
109
121
  private async waitForTestIdClickEventAfter(testId: string, options?: { timeoutMs?: number }): Promise<void> {
@@ -237,7 +249,7 @@ export class BasePage {
237
249
  }
238
250
 
239
251
  /**
240
- * Animates the cursor to an element.
252
+ * Animates the pointer to an element.
241
253
  */
242
254
  protected async animateCursorToElement(
243
255
  target: string | PwLocator,