@sharadox/test-web-library 0.1.0 → 0.2.0

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
@@ -9,6 +9,8 @@
9
9
  - **`describeStyleMemory`:** a style group starts on its default and remembers the visitor's choice.
10
10
  - **`describeConsent`:** Deny lasts for the visit, manual choices are kept, and the preferences link reopens the panel.
11
11
 
12
+ For Vitest component tests, `installBrowserEnvironment()` adds what happy-dom lacks: intersection and resize observers, media queries, `scrollTo`, and Motion animations that finish at once. `reportIntersection()` marks watched elements as visible.
13
+
12
14
  ## Install
13
15
 
14
16
  ```sh
@@ -0,0 +1,22 @@
1
+ import type { BrowserEnvironmentOptions, ObserverRecord } from "@sharadox/test-web-library/browser-environment/type";
2
+ /** Every IntersectionObserver created since `installBrowserEnvironment`, so tests can report elements as visible. */
3
+ export declare const observers: ObserverRecord[];
4
+ /**
5
+ * Gives a Vitest happy-dom environment the browser features components use and the DOM lacks: intersection and
6
+ * resize observers, media queries (reduced motion, fine pointer, colour scheme), `scrollTo`, and Motion animations
7
+ * that finish at once (so unmounting never cancels one mid-way). Call it in `beforeEach`, and restore with
8
+ * `vi.restoreAllMocks()` and `vi.unstubAllGlobals()`.
9
+ *
10
+ * @example
11
+ * beforeEach(() => installBrowserEnvironment({ reducedMotion: true }));
12
+ */
13
+ export declare function installBrowserEnvironment({ reducedMotion, finePointer, dark, }?: BrowserEnvironmentOptions): void;
14
+ /**
15
+ * Reports elements as visible (or not) to every observer created so far: all watched elements by default, or only
16
+ * those matching `filter`.
17
+ *
18
+ * @example
19
+ * act(() => reportIntersection());
20
+ */
21
+ export declare function reportIntersection(isIntersecting?: boolean, filter?: (element: Element) => boolean): void;
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/browser-environment/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,qDAAqD,CAAC;AAErH,qHAAqH;AACrH,eAAO,MAAM,SAAS,EAAE,cAAc,EAAO,CAAC;AAE9C;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,aAAqB,EACrB,WAAkB,EAClB,IAAW,GACZ,GAAE,yBAA8B,GAAG,IAAI,CA8CvC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,UAAO,EAAE,MAAM,GAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAoB,GAAG,IAAI,CAYlH"}
@@ -0,0 +1,68 @@
1
+ import { MotionGlobalConfig } from "motion";
2
+ import { vi } from "vitest";
3
+ /** Every IntersectionObserver created since `installBrowserEnvironment`, so tests can report elements as visible. */
4
+ export const observers = [];
5
+ /**
6
+ * Gives a Vitest happy-dom environment the browser features components use and the DOM lacks: intersection and
7
+ * resize observers, media queries (reduced motion, fine pointer, colour scheme), `scrollTo`, and Motion animations
8
+ * that finish at once (so unmounting never cancels one mid-way). Call it in `beforeEach`, and restore with
9
+ * `vi.restoreAllMocks()` and `vi.unstubAllGlobals()`.
10
+ *
11
+ * @example
12
+ * beforeEach(() => installBrowserEnvironment({ reducedMotion: true }));
13
+ */
14
+ export function installBrowserEnvironment({ reducedMotion = false, finePointer = true, dark = true, } = {}) {
15
+ MotionGlobalConfig.skipAnimations = true;
16
+ observers.length = 0;
17
+ vi.stubGlobal("IntersectionObserver", class {
18
+ record;
19
+ constructor(callback, options) {
20
+ this.record = { callback, options, elements: [] };
21
+ observers.push(this.record);
22
+ }
23
+ observe(element) {
24
+ this.record.elements.push(element);
25
+ }
26
+ unobserve() { }
27
+ disconnect() { }
28
+ takeRecords() {
29
+ return [];
30
+ }
31
+ });
32
+ vi.stubGlobal("ResizeObserver", class {
33
+ observe() { }
34
+ unobserve() { }
35
+ disconnect() { }
36
+ });
37
+ const matches = (query) => (query.includes("reduce") && reducedMotion) ||
38
+ (query.includes("pointer: fine") && finePointer) ||
39
+ (query.includes("dark") && dark) ||
40
+ (query.includes("light") && !dark);
41
+ vi.spyOn(window, "matchMedia").mockImplementation((query) => ({
42
+ matches: matches(query),
43
+ media: query,
44
+ addEventListener: () => { },
45
+ removeEventListener: () => { },
46
+ addListener: () => { },
47
+ removeListener: () => { },
48
+ }));
49
+ vi.stubGlobal("scrollTo", vi.fn());
50
+ }
51
+ /**
52
+ * Reports elements as visible (or not) to every observer created so far: all watched elements by default, or only
53
+ * those matching `filter`.
54
+ *
55
+ * @example
56
+ * act(() => reportIntersection());
57
+ */
58
+ export function reportIntersection(isIntersecting = true, filter = () => true) {
59
+ for (const observer of observers) {
60
+ const entries = observer.elements.filter(filter).map((target) => ({
61
+ isIntersecting,
62
+ target,
63
+ intersectionRatio: isIntersecting ? 1 : 0,
64
+ }));
65
+ observer.callback(entries, {});
66
+ }
67
+ }
68
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../source/browser-environment/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAG5B,qHAAqH;AACrH,MAAM,CAAC,MAAM,SAAS,GAAqB,EAAE,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CAAC,EACxC,aAAa,GAAG,KAAK,EACrB,WAAW,GAAG,IAAI,EAClB,IAAI,GAAG,IAAI,MACkB,EAAE;IAC/B,kBAAkB,CAAC,cAAc,GAAG,IAAI,CAAC;IACzC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IACrB,EAAE,CAAC,UAAU,CACX,sBAAsB,EACtB;QACE,MAAM,CAAiB;QACvB,YAAY,QAAsC,EAAE,OAAkC;YACpF,IAAI,CAAC,MAAM,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;YAClD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,OAAgB;YACtB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC;QACD,SAAS,KAAI,CAAC;QACd,UAAU,KAAI,CAAC;QACf,WAAW;YACT,OAAO,EAAE,CAAC;QACZ,CAAC;KACF,CACF,CAAC;IACF,EAAE,CAAC,UAAU,CACX,gBAAgB,EAChB;QACE,OAAO,KAAI,CAAC;QACZ,SAAS,KAAI,CAAC;QACd,UAAU,KAAI,CAAC;KAChB,CACF,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE,CAChC,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,aAAa,CAAC;QAC3C,CAAC,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,WAAW,CAAC;QAChD,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;QAChC,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,kBAAkB,CAC/C,CAAC,KAAa,EAAE,EAAE,CAChB,CAAC;QACC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC;QACvB,KAAK,EAAE,KAAK;QACZ,gBAAgB,EAAE,GAAG,EAAE,GAAE,CAAC;QAC1B,mBAAmB,EAAE,GAAG,EAAE,GAAE,CAAC;QAC7B,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;QACrB,cAAc,EAAE,GAAG,EAAE,GAAE,CAAC;KACzB,CAA8B,CAClC,CAAC;IACF,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,cAAc,GAAG,IAAI,EAAE,SAAwC,GAAG,EAAE,CAAC,IAAI;IAC1G,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAClD,CAAC,MAAM,EAAE,EAAE,CACT,CAAC;YACC,cAAc;YACd,MAAM;YACN,iBAAiB,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC1C,CAAyC,CAC7C,CAAC;QACF,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,EAA0B,CAAC,CAAC;IACzD,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface BrowserEnvironmentOptions {
2
+ /** Whether `prefers-reduced-motion: reduce` matches; false by default. */
3
+ reducedMotion?: boolean;
4
+ /** Whether `pointer: fine` matches (mouse or trackpad); true by default. */
5
+ finePointer?: boolean;
6
+ /** Whether `prefers-color-scheme: dark` matches; true by default (light matches otherwise). */
7
+ dark?: boolean;
8
+ }
9
+ /** An IntersectionObserver created during a test, with the elements it watches. */
10
+ export interface ObserverRecord {
11
+ callback: IntersectionObserverCallback;
12
+ options?: IntersectionObserverInit;
13
+ elements: Element[];
14
+ }
15
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../source/browser-environment/type.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,yBAAyB;IACxC,0EAA0E;IAC1E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+FAA+F;IAC/F,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,4BAA4B,CAAC;IACvC,OAAO,CAAC,EAAE,wBAAwB,CAAC;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.js","sourceRoot":"","sources":["../../source/browser-environment/type.ts"],"names":[],"mappings":""}
package/llms.txt CHANGED
@@ -7,3 +7,4 @@
7
7
  - /installability-suite describeInstallability(path) (Chromium CDP Page.getInstallabilityErrors)
8
8
  - /preference-suite describeLocaleMemory({ browserLocale, automaticPath, switchName, pick: { link, path } }), describeStyleMemory({ path, attribute, switchName, colorScheme, initial, pick: { radio, style } }), describeConsent({ path, dialogName, deny, customize, save, preferences, category, storageKey, expectedGranted })
9
9
  - Peer: @playwright/test. Licence: Sharadox Open Source Licence 0.1.0
10
+ - /browser-environment installBrowserEnvironment({ reducedMotion=false, finePointer=true, dark=true }) (Vitest happy-dom: IntersectionObserver/ResizeObserver stubs, matchMedia, scrollTo, Motion skipAnimations); reportIntersection(isIntersecting=true, filter?); observers
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sharadox/test-web-library",
3
- "version": "0.1.0",
4
- "description": "Playwright helpers for websites: deterministic visual snapshots and shared end-to-end suites for search engines, installability, remembered language and style, and cookie consent.",
3
+ "version": "0.2.0",
4
+ "description": "Test helpers for websites: Playwright visual snapshots and shared end-to-end suites (search engines, installability, remembered language and style, cookie consent), and a browser environment for Vitest component tests.",
5
5
  "private": false,
6
6
  "license": "SEE LICENSE IN LICENCA.md",
7
7
  "author": "Sharadox <contact@sharadox.com>",
@@ -56,8 +56,10 @@
56
56
  "@types/node": "^24.19.2",
57
57
  "@vitest/coverage-v8": "^5.1.0",
58
58
  "eslint": "9.39.5",
59
+ "happy-dom": "^21.0.4",
59
60
  "husky": "^9.1.7",
60
61
  "lint-staged": "^17.6.0",
62
+ "motion": "^12.43.0",
61
63
  "prettier": "^3.10.0",
62
64
  "turbo": "^2.11.10",
63
65
  "typedoc": "^0.28.20",
@@ -68,7 +70,20 @@
68
70
  "@sharadox/web-library": "^0.5.0"
69
71
  },
70
72
  "peerDependencies": {
71
- "@playwright/test": ">=1.50"
73
+ "@playwright/test": ">=1.50",
74
+ "vitest": ">=5",
75
+ "motion": ">=12"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "@playwright/test": {
79
+ "optional": true
80
+ },
81
+ "vitest": {
82
+ "optional": true
83
+ },
84
+ "motion": {
85
+ "optional": true
86
+ }
72
87
  },
73
88
  "scripts": {
74
89
  "build": "rm -rf distribution && tsc -p tsconfig.build.json",