@lytjs/test-utils 6.5.0 → 6.6.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/dist/index.cjs ADDED
@@ -0,0 +1,279 @@
1
+ 'use strict';
2
+
3
+ var core = require('@lytjs/core');
4
+ var reactivity = require('@lytjs/reactivity');
5
+
6
+ // src/index.ts
7
+ function createDOMEnvironment() {
8
+ if (typeof document !== "undefined") {
9
+ return document;
10
+ }
11
+ throw new Error(
12
+ "DOM environment not available. Please install jsdom or happy-dom and set up your test environment."
13
+ );
14
+ }
15
+ function createContainer() {
16
+ const doc = createDOMEnvironment();
17
+ const container = doc.createElement("div");
18
+ container.id = "test-container";
19
+ return container;
20
+ }
21
+ function mount(component, options = {}) {
22
+ const container = createContainer();
23
+ const doc = createDOMEnvironment();
24
+ let targetEl;
25
+ if (options.attachTo) {
26
+ if (typeof options.attachTo === "string") {
27
+ targetEl = doc.querySelector(options.attachTo);
28
+ if (!targetEl) {
29
+ throw new Error(`Element not found: ${options.attachTo}`);
30
+ }
31
+ } else {
32
+ targetEl = options.attachTo;
33
+ }
34
+ } else {
35
+ targetEl = container;
36
+ }
37
+ const app = core.createApp(component);
38
+ if (options.global?.plugins) {
39
+ for (const plugin of options.global.plugins) {
40
+ if (typeof plugin === "function") {
41
+ plugin(app);
42
+ } else if (plugin && typeof plugin.install === "function") {
43
+ plugin.install(app);
44
+ }
45
+ }
46
+ }
47
+ if (options.global?.provide) {
48
+ for (const [key, value] of Object.entries(options.global.provide)) {
49
+ app.provide(key, value);
50
+ }
51
+ }
52
+ const instance = app.mount(targetEl);
53
+ const wrapper = {
54
+ root: targetEl.firstElementChild || targetEl,
55
+ vm: instance,
56
+ unmount() {
57
+ app.unmount();
58
+ if (targetEl === container && container.parentNode) {
59
+ container.parentNode.removeChild(container);
60
+ }
61
+ },
62
+ find(selector) {
63
+ return this.root.querySelector(selector);
64
+ },
65
+ findAll(selector) {
66
+ return Array.from(this.root.querySelectorAll(selector));
67
+ },
68
+ props() {
69
+ return { ...options.props };
70
+ },
71
+ async setProps(newProps) {
72
+ Object.assign(options.props || {}, newProps);
73
+ await flushPromises();
74
+ },
75
+ trigger(selector, event, detail) {
76
+ const el = this.find(selector);
77
+ if (!el) {
78
+ throw new Error(`Element not found: ${selector}`);
79
+ }
80
+ const eventObj = new CustomEvent(event, { detail, bubbles: true });
81
+ el.dispatchEvent(eventObj);
82
+ },
83
+ text() {
84
+ return this.root.textContent || "";
85
+ },
86
+ html() {
87
+ return this.root.innerHTML;
88
+ }
89
+ };
90
+ return wrapper;
91
+ }
92
+ async function mountAsync(component, options = {}) {
93
+ const wrapper = mount(component, options);
94
+ await flushPromises();
95
+ return wrapper;
96
+ }
97
+ function flushPromises() {
98
+ return new Promise((resolve) => setTimeout(resolve, 0));
99
+ }
100
+ async function waitFor(condition, options = {}) {
101
+ const { timeout = 1e3, interval = 50 } = options;
102
+ const startTime = Date.now();
103
+ while (!condition()) {
104
+ if (Date.now() - startTime > timeout) {
105
+ throw new Error(`waitFor timeout: condition not met within ${timeout}ms`);
106
+ }
107
+ await new Promise((resolve) => setTimeout(resolve, interval));
108
+ }
109
+ }
110
+ function wait(ms) {
111
+ return new Promise((resolve) => setTimeout(resolve, ms));
112
+ }
113
+ function mockFn(implementation) {
114
+ const calls = [];
115
+ const instances = [];
116
+ let mockImplementation = implementation;
117
+ const fn = function(...args) {
118
+ calls.push(args);
119
+ instances.push(this);
120
+ if (mockImplementation) {
121
+ return mockImplementation.apply(this, args);
122
+ }
123
+ return void 0;
124
+ };
125
+ fn.calls = calls;
126
+ fn.instances = instances;
127
+ fn.mockReturnValue = (value) => {
128
+ mockImplementation = (() => value);
129
+ return fn;
130
+ };
131
+ fn.mockResolvedValue = (value) => {
132
+ mockImplementation = (async () => value);
133
+ return fn;
134
+ };
135
+ fn.mockImplementation = (impl) => {
136
+ mockImplementation = impl;
137
+ return fn;
138
+ };
139
+ fn.mockClear = () => {
140
+ calls.length = 0;
141
+ instances.length = 0;
142
+ return fn;
143
+ };
144
+ fn.mockReset = () => {
145
+ fn.mockClear();
146
+ mockImplementation = implementation;
147
+ return fn;
148
+ };
149
+ return fn;
150
+ }
151
+ function mockComponent(name, options = {}) {
152
+ return core.defineComponent({
153
+ name,
154
+ setup(_props, { slots }) {
155
+ return () => core.h(
156
+ "div",
157
+ { "data-testid": `mock-${name.toLowerCase()}` },
158
+ slots.default ? slots.default() : `Mock: ${name}`
159
+ );
160
+ },
161
+ ...options
162
+ });
163
+ }
164
+ function spyOn(obj, method, implementation) {
165
+ const original = obj[method];
166
+ const mock = mockFn(implementation);
167
+ Object.defineProperty(obj, method, {
168
+ value: mock,
169
+ writable: true,
170
+ configurable: true
171
+ });
172
+ mock.mockRestore = () => {
173
+ Object.defineProperty(obj, method, {
174
+ value: original,
175
+ writable: true,
176
+ configurable: true
177
+ });
178
+ };
179
+ return mock;
180
+ }
181
+ function createTestSignal(initialValue) {
182
+ const sig = reactivity.signal(initialValue);
183
+ const history = [initialValue];
184
+ const testSignal = {
185
+ get value() {
186
+ return sig();
187
+ },
188
+ set value(v) {
189
+ sig.set(v);
190
+ history.push(v);
191
+ },
192
+ history: () => [...history],
193
+ reset: () => {
194
+ sig.set(initialValue);
195
+ history.length = 0;
196
+ history.push(initialValue);
197
+ }
198
+ };
199
+ return testSignal;
200
+ }
201
+ function trackSignal(sig) {
202
+ const values = [];
203
+ const stop = reactivity.watch(
204
+ () => sig(),
205
+ (value) => {
206
+ values.push({ value, timestamp: Date.now() });
207
+ }
208
+ );
209
+ return {
210
+ values: () => values.map((v) => v.value),
211
+ timestamps: () => values.map((v) => v.timestamp),
212
+ count: () => values.length,
213
+ stop
214
+ };
215
+ }
216
+ async function assertEmits(wrapper, eventName, action, timeout = 1e3) {
217
+ const emitted = [];
218
+ const originalEmit = wrapper.vm?.$emit;
219
+ if (originalEmit) {
220
+ wrapper.vm.$emit = (event, ...args) => {
221
+ if (event === eventName) {
222
+ emitted.push(args.length === 1 ? args[0] : args);
223
+ }
224
+ originalEmit(event, ...args);
225
+ };
226
+ }
227
+ await action();
228
+ await flushPromises();
229
+ if (emitted.length === 0) {
230
+ throw new Error(`Event "${eventName}" was not emitted within ${timeout}ms`);
231
+ }
232
+ return emitted;
233
+ }
234
+ function isReactive(_value) {
235
+ return false;
236
+ }
237
+ var cleanupCallbacks = [];
238
+ function registerCleanup(callback) {
239
+ cleanupCallbacks.push(callback);
240
+ }
241
+ function runCleanup() {
242
+ for (const callback of cleanupCallbacks) {
243
+ try {
244
+ callback();
245
+ } catch (error) {
246
+ console.error("Cleanup error:", error);
247
+ }
248
+ }
249
+ cleanupCallbacks = [];
250
+ }
251
+ function mountWithCleanup(component, options = {}) {
252
+ const wrapper = mount(component, options);
253
+ registerCleanup(() => wrapper.unmount());
254
+ return wrapper;
255
+ }
256
+
257
+ Object.defineProperty(exports, "nextTick", {
258
+ enumerable: true,
259
+ get: function () { return core.nextTick; }
260
+ });
261
+ exports.assertEmits = assertEmits;
262
+ exports.createContainer = createContainer;
263
+ exports.createDOMEnvironment = createDOMEnvironment;
264
+ exports.createTestSignal = createTestSignal;
265
+ exports.flushPromises = flushPromises;
266
+ exports.isReactive = isReactive;
267
+ exports.mockComponent = mockComponent;
268
+ exports.mockFn = mockFn;
269
+ exports.mount = mount;
270
+ exports.mountAsync = mountAsync;
271
+ exports.mountWithCleanup = mountWithCleanup;
272
+ exports.registerCleanup = registerCleanup;
273
+ exports.runCleanup = runCleanup;
274
+ exports.spyOn = spyOn;
275
+ exports.trackSignal = trackSignal;
276
+ exports.wait = wait;
277
+ exports.waitFor = waitFor;
278
+ //# sourceMappingURL=index.cjs.map
279
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["createApp","defineComponent","h","signal","watch"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lytjs/test-utils\n *\n * LytJS testing utilities for unit and integration testing.\n *\n * @packageDocumentation\n */\n\nimport { createApp, defineComponent, h, nextTick, type ComponentOptions } from '@lytjs/core';\nimport { signal, watch, type Signal } from '@lytjs/reactivity';\n\n// ============================================\n// Types\n// ============================================\n\nexport interface MountOptions {\n /** Attach to a specific DOM element */\n attachTo?: HTMLElement | string;\n /** Props to pass to the component */\n props?: Record<string, unknown>;\n /** Global plugins to install */\n global?: {\n plugins?: unknown[];\n provide?: Record<string, unknown>;\n };\n}\n\nexport interface Wrapper<T = unknown> {\n /** The root DOM element of the mounted component */\n root: HTMLElement;\n /** The component instance */\n vm: T;\n /** Unmount the component */\n unmount: () => void;\n /** Get DOM element by selector */\n find: (selector: string) => Element | null;\n /** Get all DOM elements matching selector */\n findAll: (selector: string) => Element[];\n /** Get component props */\n props: () => Record<string, unknown>;\n /** Set component props */\n setProps: (props: Record<string, unknown>) => Promise<void>;\n /** Trigger an event on an element */\n trigger: (selector: string, event: string, detail?: unknown) => void;\n /** Get element text content */\n text: () => string;\n /** Get element inner HTML */\n html: () => string;\n}\n\n// ============================================\n// DOM Utilities\n// ============================================\n\n/**\n * Create a clean DOM environment for testing\n */\nexport function createDOMEnvironment(): Document {\n if (typeof document !== 'undefined') {\n return document;\n }\n\n // For Node.js environment, you would need jsdom or happy-dom\n throw new Error(\n 'DOM environment not available. Please install jsdom or happy-dom and set up your test environment.',\n );\n}\n\n/**\n * Create a container element for mounting\n */\nexport function createContainer(): HTMLElement {\n const doc = createDOMEnvironment();\n const container = doc.createElement('div');\n container.id = 'test-container';\n return container;\n}\n\n// ============================================\n// Mount Utilities\n// ============================================\n\n/**\n * Mount a component for testing\n *\n * @example\n * ```ts\n * import { mount } from '@lytjs/test-utils';\n * import MyComponent from './MyComponent.lyt';\n *\n * const wrapper = mount(MyComponent, {\n * props: { title: 'Hello' }\n * });\n *\n * expect(wrapper.text()).toContain('Hello');\n * wrapper.unmount();\n * ```\n */\nexport function mount<T = unknown>(component: unknown, options: MountOptions = {}): Wrapper<T> {\n const container = createContainer();\n const doc = createDOMEnvironment();\n\n // Handle attachTo option\n let targetEl: HTMLElement;\n if (options.attachTo) {\n if (typeof options.attachTo === 'string') {\n targetEl = doc.querySelector(options.attachTo) as HTMLElement;\n if (!targetEl) {\n throw new Error(`Element not found: ${options.attachTo}`);\n }\n } else {\n targetEl = options.attachTo;\n }\n } else {\n targetEl = container;\n }\n\n // Create the app\n const app: {\n use: (plugin: unknown) => void;\n provide: (key: string, value: unknown) => void;\n mount: (el: HTMLElement) => T;\n unmount: () => void;\n } = createApp(component as Parameters<typeof createApp>[0]);\n\n // Install global plugins\n if (options.global?.plugins) {\n for (const plugin of options.global.plugins) {\n if (typeof plugin === 'function') {\n plugin(app);\n } else if (plugin && typeof plugin.install === 'function') {\n plugin.install(app);\n }\n }\n }\n\n // Provide global values\n if (options.global?.provide) {\n for (const [key, value] of Object.entries(options.global.provide)) {\n app.provide(key, value);\n }\n }\n\n // Mount the app\n const instance = app.mount(targetEl);\n\n // Create wrapper\n const wrapper: Wrapper<T> = {\n root: (targetEl.firstElementChild as HTMLElement) || targetEl,\n vm: instance as T,\n\n unmount() {\n app.unmount();\n if (targetEl === container && container.parentNode) {\n container.parentNode.removeChild(container);\n }\n },\n\n find(selector: string) {\n return this.root.querySelector(selector);\n },\n\n findAll(selector: string) {\n return Array.from(this.root.querySelectorAll(selector));\n },\n\n props() {\n return { ...options.props };\n },\n\n async setProps(newProps: Record<string, unknown>) {\n Object.assign(options.props || {}, newProps);\n await flushPromises();\n },\n\n trigger(selector: string, event: string, detail?: unknown) {\n const el = this.find(selector);\n if (!el) {\n throw new Error(`Element not found: ${selector}`);\n }\n\n const eventObj = new CustomEvent(event, { detail, bubbles: true });\n el.dispatchEvent(eventObj);\n },\n\n text() {\n return this.root.textContent || '';\n },\n\n html() {\n return this.root.innerHTML;\n },\n };\n\n return wrapper;\n}\n\n/**\n * Mount a component and return a promise that resolves after initial render\n */\nexport async function mountAsync<T = unknown>(\n component: unknown,\n options: MountOptions = {},\n): Promise<Wrapper<T>> {\n const wrapper = mount<T>(component, options);\n await flushPromises();\n return wrapper;\n}\n\n// ============================================\n// Async Utilities\n// ============================================\n\n/**\n * Wait for all pending promises to resolve\n *\n * @example\n * ```ts\n * await flushPromises();\n * ```\n */\nexport function flushPromises(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, 0));\n}\n\n/**\n * Wait for the next tick\n *\n * @example\n * ```ts\n * await nextTick();\n * ```\n */\nexport { nextTick };\n\n/**\n * Wait for a specific condition to be true\n *\n * @example\n * ```ts\n * await waitFor(() => wrapper.text().includes('Loaded'));\n * ```\n */\nexport async function waitFor(\n condition: () => boolean,\n options: { timeout?: number; interval?: number } = {},\n): Promise<void> {\n const { timeout = 1000, interval = 50 } = options;\n\n const startTime = Date.now();\n\n while (!condition()) {\n if (Date.now() - startTime > timeout) {\n throw new Error(`waitFor timeout: condition not met within ${timeout}ms`);\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n}\n\n/**\n * Wait for a specific amount of time\n */\nexport function wait(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ============================================\n// Mock Utilities\n// ============================================\n\n/**\n * Create a mock function that tracks calls\n */\nexport function mockFn<T extends (...args: unknown[]) => unknown = (...args: unknown[]) => unknown>(\n implementation?: T,\n): MockFunction<T> {\n const calls: unknown[][] = [];\n const instances: unknown[] = [];\n let mockImplementation = implementation;\n\n const fn: MockFunction<T> = function (this: unknown, ...args: unknown[]) {\n calls.push(args);\n instances.push(this);\n if (mockImplementation) {\n return mockImplementation.apply(this, args);\n }\n return undefined;\n } as MockFunction<T>;\n\n fn.calls = calls;\n fn.instances = instances;\n\n fn.mockReturnValue = (value: unknown) => {\n mockImplementation = (() => value) as T;\n return fn;\n };\n\n fn.mockResolvedValue = (value: unknown) => {\n mockImplementation = (async () => value) as T;\n return fn;\n };\n\n fn.mockImplementation = (impl: T) => {\n mockImplementation = impl;\n return fn;\n };\n\n fn.mockClear = () => {\n calls.length = 0;\n instances.length = 0;\n return fn;\n };\n\n fn.mockReset = () => {\n fn.mockClear();\n mockImplementation = implementation;\n return fn;\n };\n\n return fn;\n}\n\nexport interface MockFunction<\n T extends (...args: unknown[]) => unknown = (...args: unknown[]) => unknown,\n> {\n (...args: Parameters<T>): ReturnType<T>;\n calls: unknown[][];\n instances: unknown[];\n mockReturnValue: (value: unknown) => MockFunction<T>;\n mockResolvedValue: (value: unknown) => MockFunction<T>;\n mockImplementation: (impl: T) => MockFunction<T>;\n mockClear: () => MockFunction<T>;\n mockReset: () => MockFunction<T>;\n}\n\n/**\n * Create a mock component for testing\n */\nexport function mockComponent(\n name: string,\n options: Partial<ComponentOptions> = {},\n): ComponentOptions {\n return defineComponent({\n name,\n setup(_props, { slots }) {\n return () =>\n (\n h as unknown as (\n tag: string,\n attrs: Record<string, unknown>,\n children?: unknown,\n ) => unknown\n )(\n 'div',\n { 'data-testid': `mock-${name.toLowerCase()}` },\n slots.default ? slots.default() : `Mock: ${name}`,\n );\n },\n ...options,\n });\n}\n\n/**\n * Spy on an object method\n */\nexport function spyOn<T extends object, K extends keyof T>(\n obj: T,\n method: K,\n implementation?: T[K],\n): MockFunction {\n const original = obj[method];\n const mock = mockFn(implementation as (...args: unknown[]) => unknown);\n\n Object.defineProperty(obj, method, {\n value: mock,\n writable: true,\n configurable: true,\n });\n\n (mock as unknown as { mockRestore: () => void }).mockRestore = () => {\n Object.defineProperty(obj, method, {\n value: original,\n writable: true,\n configurable: true,\n });\n };\n\n return mock;\n}\n\n// ============================================\n// Signal Testing Utilities\n// ============================================\n\n/**\n * Create a test signal with tracking\n */\nexport function createTestSignal<T>(initialValue: T): TestSignal<T> {\n const sig = signal(initialValue);\n const history: T[] = [initialValue];\n\n const testSignal: TestSignal<T> = {\n get value() {\n return sig();\n },\n set value(v: T) {\n sig.set(v);\n history.push(v);\n },\n history: () => [...history],\n reset: () => {\n sig.set(initialValue);\n history.length = 0;\n history.push(initialValue);\n },\n };\n\n return testSignal;\n}\n\nexport interface TestSignal<T> {\n value: T;\n history: () => T[];\n reset: () => void;\n}\n\n/**\n * Track signal changes\n */\nexport function trackSignal<T>(sig: Signal<T>): SignalTracker<T> {\n const values: { value: T; timestamp: number }[] = [];\n const stop = watch(\n () => sig(),\n (value) => {\n values.push({ value, timestamp: Date.now() });\n },\n );\n\n return {\n values: () => values.map((v) => v.value),\n timestamps: () => values.map((v) => v.timestamp),\n count: () => values.length,\n stop,\n };\n}\n\nexport interface SignalTracker<T> {\n values: () => T[];\n timestamps: () => number[];\n count: () => number;\n stop: () => void;\n}\n\n// ============================================\n// Assertion Helpers\n// ============================================\n\n/**\n * Assert that a component emits an event\n */\nexport async function assertEmits(\n wrapper: Wrapper,\n eventName: string,\n action: () => void | Promise<void>,\n timeout = 1000,\n): Promise<unknown[]> {\n const emitted: unknown[] = [];\n\n const originalEmit = (wrapper.vm as { $emit?: (event: string, ...args: unknown[]) => void })\n ?.$emit;\n if (originalEmit) {\n (wrapper.vm as { $emit: (event: string, ...args: unknown[]) => void }).$emit = (\n event: string,\n ...args: unknown[]\n ) => {\n if (event === eventName) {\n emitted.push(args.length === 1 ? args[0] : args);\n }\n originalEmit(event, ...args);\n };\n }\n\n await action();\n await flushPromises();\n\n if (emitted.length === 0) {\n throw new Error(`Event \"${eventName}\" was not emitted within ${timeout}ms`);\n }\n\n return emitted;\n}\n\n/**\n * Check if a value is reactive\n */\nexport function isReactive(_value: unknown): boolean {\n return false; // LytJS v6 uses Signals instead of Reactive Objects\n}\n\n// ============================================\n// Cleanup Utilities\n// ============================================\n\nlet cleanupCallbacks: (() => void)[] = [];\n\n/**\n * Register a cleanup callback to run after each test\n */\nexport function registerCleanup(callback: () => void): void {\n cleanupCallbacks.push(callback);\n}\n\n/**\n * Run all cleanup callbacks\n */\nexport function runCleanup(): void {\n for (const callback of cleanupCallbacks) {\n try {\n callback();\n } catch (error) {\n console.error('Cleanup error:', error);\n }\n }\n cleanupCallbacks = [];\n}\n\n/**\n * Auto-cleanup wrapper for mount\n */\nexport function mountWithCleanup<T = unknown>(\n component: unknown,\n options: MountOptions = {},\n): Wrapper<T> {\n const wrapper = mount<T>(component, options);\n registerCleanup(() => wrapper.unmount());\n return wrapper;\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,258 @@
1
+ import { createApp, defineComponent, h } from '@lytjs/core';
2
+ export { nextTick } from '@lytjs/core';
3
+ import { signal, watch } from '@lytjs/reactivity';
4
+
5
+ // src/index.ts
6
+ function createDOMEnvironment() {
7
+ if (typeof document !== "undefined") {
8
+ return document;
9
+ }
10
+ throw new Error(
11
+ "DOM environment not available. Please install jsdom or happy-dom and set up your test environment."
12
+ );
13
+ }
14
+ function createContainer() {
15
+ const doc = createDOMEnvironment();
16
+ const container = doc.createElement("div");
17
+ container.id = "test-container";
18
+ return container;
19
+ }
20
+ function mount(component, options = {}) {
21
+ const container = createContainer();
22
+ const doc = createDOMEnvironment();
23
+ let targetEl;
24
+ if (options.attachTo) {
25
+ if (typeof options.attachTo === "string") {
26
+ targetEl = doc.querySelector(options.attachTo);
27
+ if (!targetEl) {
28
+ throw new Error(`Element not found: ${options.attachTo}`);
29
+ }
30
+ } else {
31
+ targetEl = options.attachTo;
32
+ }
33
+ } else {
34
+ targetEl = container;
35
+ }
36
+ const app = createApp(component);
37
+ if (options.global?.plugins) {
38
+ for (const plugin of options.global.plugins) {
39
+ if (typeof plugin === "function") {
40
+ plugin(app);
41
+ } else if (plugin && typeof plugin.install === "function") {
42
+ plugin.install(app);
43
+ }
44
+ }
45
+ }
46
+ if (options.global?.provide) {
47
+ for (const [key, value] of Object.entries(options.global.provide)) {
48
+ app.provide(key, value);
49
+ }
50
+ }
51
+ const instance = app.mount(targetEl);
52
+ const wrapper = {
53
+ root: targetEl.firstElementChild || targetEl,
54
+ vm: instance,
55
+ unmount() {
56
+ app.unmount();
57
+ if (targetEl === container && container.parentNode) {
58
+ container.parentNode.removeChild(container);
59
+ }
60
+ },
61
+ find(selector) {
62
+ return this.root.querySelector(selector);
63
+ },
64
+ findAll(selector) {
65
+ return Array.from(this.root.querySelectorAll(selector));
66
+ },
67
+ props() {
68
+ return { ...options.props };
69
+ },
70
+ async setProps(newProps) {
71
+ Object.assign(options.props || {}, newProps);
72
+ await flushPromises();
73
+ },
74
+ trigger(selector, event, detail) {
75
+ const el = this.find(selector);
76
+ if (!el) {
77
+ throw new Error(`Element not found: ${selector}`);
78
+ }
79
+ const eventObj = new CustomEvent(event, { detail, bubbles: true });
80
+ el.dispatchEvent(eventObj);
81
+ },
82
+ text() {
83
+ return this.root.textContent || "";
84
+ },
85
+ html() {
86
+ return this.root.innerHTML;
87
+ }
88
+ };
89
+ return wrapper;
90
+ }
91
+ async function mountAsync(component, options = {}) {
92
+ const wrapper = mount(component, options);
93
+ await flushPromises();
94
+ return wrapper;
95
+ }
96
+ function flushPromises() {
97
+ return new Promise((resolve) => setTimeout(resolve, 0));
98
+ }
99
+ async function waitFor(condition, options = {}) {
100
+ const { timeout = 1e3, interval = 50 } = options;
101
+ const startTime = Date.now();
102
+ while (!condition()) {
103
+ if (Date.now() - startTime > timeout) {
104
+ throw new Error(`waitFor timeout: condition not met within ${timeout}ms`);
105
+ }
106
+ await new Promise((resolve) => setTimeout(resolve, interval));
107
+ }
108
+ }
109
+ function wait(ms) {
110
+ return new Promise((resolve) => setTimeout(resolve, ms));
111
+ }
112
+ function mockFn(implementation) {
113
+ const calls = [];
114
+ const instances = [];
115
+ let mockImplementation = implementation;
116
+ const fn = function(...args) {
117
+ calls.push(args);
118
+ instances.push(this);
119
+ if (mockImplementation) {
120
+ return mockImplementation.apply(this, args);
121
+ }
122
+ return void 0;
123
+ };
124
+ fn.calls = calls;
125
+ fn.instances = instances;
126
+ fn.mockReturnValue = (value) => {
127
+ mockImplementation = (() => value);
128
+ return fn;
129
+ };
130
+ fn.mockResolvedValue = (value) => {
131
+ mockImplementation = (async () => value);
132
+ return fn;
133
+ };
134
+ fn.mockImplementation = (impl) => {
135
+ mockImplementation = impl;
136
+ return fn;
137
+ };
138
+ fn.mockClear = () => {
139
+ calls.length = 0;
140
+ instances.length = 0;
141
+ return fn;
142
+ };
143
+ fn.mockReset = () => {
144
+ fn.mockClear();
145
+ mockImplementation = implementation;
146
+ return fn;
147
+ };
148
+ return fn;
149
+ }
150
+ function mockComponent(name, options = {}) {
151
+ return defineComponent({
152
+ name,
153
+ setup(_props, { slots }) {
154
+ return () => h(
155
+ "div",
156
+ { "data-testid": `mock-${name.toLowerCase()}` },
157
+ slots.default ? slots.default() : `Mock: ${name}`
158
+ );
159
+ },
160
+ ...options
161
+ });
162
+ }
163
+ function spyOn(obj, method, implementation) {
164
+ const original = obj[method];
165
+ const mock = mockFn(implementation);
166
+ Object.defineProperty(obj, method, {
167
+ value: mock,
168
+ writable: true,
169
+ configurable: true
170
+ });
171
+ mock.mockRestore = () => {
172
+ Object.defineProperty(obj, method, {
173
+ value: original,
174
+ writable: true,
175
+ configurable: true
176
+ });
177
+ };
178
+ return mock;
179
+ }
180
+ function createTestSignal(initialValue) {
181
+ const sig = signal(initialValue);
182
+ const history = [initialValue];
183
+ const testSignal = {
184
+ get value() {
185
+ return sig();
186
+ },
187
+ set value(v) {
188
+ sig.set(v);
189
+ history.push(v);
190
+ },
191
+ history: () => [...history],
192
+ reset: () => {
193
+ sig.set(initialValue);
194
+ history.length = 0;
195
+ history.push(initialValue);
196
+ }
197
+ };
198
+ return testSignal;
199
+ }
200
+ function trackSignal(sig) {
201
+ const values = [];
202
+ const stop = watch(
203
+ () => sig(),
204
+ (value) => {
205
+ values.push({ value, timestamp: Date.now() });
206
+ }
207
+ );
208
+ return {
209
+ values: () => values.map((v) => v.value),
210
+ timestamps: () => values.map((v) => v.timestamp),
211
+ count: () => values.length,
212
+ stop
213
+ };
214
+ }
215
+ async function assertEmits(wrapper, eventName, action, timeout = 1e3) {
216
+ const emitted = [];
217
+ const originalEmit = wrapper.vm?.$emit;
218
+ if (originalEmit) {
219
+ wrapper.vm.$emit = (event, ...args) => {
220
+ if (event === eventName) {
221
+ emitted.push(args.length === 1 ? args[0] : args);
222
+ }
223
+ originalEmit(event, ...args);
224
+ };
225
+ }
226
+ await action();
227
+ await flushPromises();
228
+ if (emitted.length === 0) {
229
+ throw new Error(`Event "${eventName}" was not emitted within ${timeout}ms`);
230
+ }
231
+ return emitted;
232
+ }
233
+ function isReactive(_value) {
234
+ return false;
235
+ }
236
+ var cleanupCallbacks = [];
237
+ function registerCleanup(callback) {
238
+ cleanupCallbacks.push(callback);
239
+ }
240
+ function runCleanup() {
241
+ for (const callback of cleanupCallbacks) {
242
+ try {
243
+ callback();
244
+ } catch (error) {
245
+ console.error("Cleanup error:", error);
246
+ }
247
+ }
248
+ cleanupCallbacks = [];
249
+ }
250
+ function mountWithCleanup(component, options = {}) {
251
+ const wrapper = mount(component, options);
252
+ registerCleanup(() => wrapper.unmount());
253
+ return wrapper;
254
+ }
255
+
256
+ export { assertEmits, createContainer, createDOMEnvironment, createTestSignal, flushPromises, isReactive, mockComponent, mockFn, mount, mountAsync, mountWithCleanup, registerCleanup, runCleanup, spyOn, trackSignal, wait, waitFor };
257
+ //# sourceMappingURL=index.mjs.map
258
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @lytjs/test-utils\n *\n * LytJS testing utilities for unit and integration testing.\n *\n * @packageDocumentation\n */\n\nimport { createApp, defineComponent, h, nextTick, type ComponentOptions } from '@lytjs/core';\nimport { signal, watch, type Signal } from '@lytjs/reactivity';\n\n// ============================================\n// Types\n// ============================================\n\nexport interface MountOptions {\n /** Attach to a specific DOM element */\n attachTo?: HTMLElement | string;\n /** Props to pass to the component */\n props?: Record<string, unknown>;\n /** Global plugins to install */\n global?: {\n plugins?: unknown[];\n provide?: Record<string, unknown>;\n };\n}\n\nexport interface Wrapper<T = unknown> {\n /** The root DOM element of the mounted component */\n root: HTMLElement;\n /** The component instance */\n vm: T;\n /** Unmount the component */\n unmount: () => void;\n /** Get DOM element by selector */\n find: (selector: string) => Element | null;\n /** Get all DOM elements matching selector */\n findAll: (selector: string) => Element[];\n /** Get component props */\n props: () => Record<string, unknown>;\n /** Set component props */\n setProps: (props: Record<string, unknown>) => Promise<void>;\n /** Trigger an event on an element */\n trigger: (selector: string, event: string, detail?: unknown) => void;\n /** Get element text content */\n text: () => string;\n /** Get element inner HTML */\n html: () => string;\n}\n\n// ============================================\n// DOM Utilities\n// ============================================\n\n/**\n * Create a clean DOM environment for testing\n */\nexport function createDOMEnvironment(): Document {\n if (typeof document !== 'undefined') {\n return document;\n }\n\n // For Node.js environment, you would need jsdom or happy-dom\n throw new Error(\n 'DOM environment not available. Please install jsdom or happy-dom and set up your test environment.',\n );\n}\n\n/**\n * Create a container element for mounting\n */\nexport function createContainer(): HTMLElement {\n const doc = createDOMEnvironment();\n const container = doc.createElement('div');\n container.id = 'test-container';\n return container;\n}\n\n// ============================================\n// Mount Utilities\n// ============================================\n\n/**\n * Mount a component for testing\n *\n * @example\n * ```ts\n * import { mount } from '@lytjs/test-utils';\n * import MyComponent from './MyComponent.lyt';\n *\n * const wrapper = mount(MyComponent, {\n * props: { title: 'Hello' }\n * });\n *\n * expect(wrapper.text()).toContain('Hello');\n * wrapper.unmount();\n * ```\n */\nexport function mount<T = unknown>(component: unknown, options: MountOptions = {}): Wrapper<T> {\n const container = createContainer();\n const doc = createDOMEnvironment();\n\n // Handle attachTo option\n let targetEl: HTMLElement;\n if (options.attachTo) {\n if (typeof options.attachTo === 'string') {\n targetEl = doc.querySelector(options.attachTo) as HTMLElement;\n if (!targetEl) {\n throw new Error(`Element not found: ${options.attachTo}`);\n }\n } else {\n targetEl = options.attachTo;\n }\n } else {\n targetEl = container;\n }\n\n // Create the app\n const app: {\n use: (plugin: unknown) => void;\n provide: (key: string, value: unknown) => void;\n mount: (el: HTMLElement) => T;\n unmount: () => void;\n } = createApp(component as Parameters<typeof createApp>[0]);\n\n // Install global plugins\n if (options.global?.plugins) {\n for (const plugin of options.global.plugins) {\n if (typeof plugin === 'function') {\n plugin(app);\n } else if (plugin && typeof plugin.install === 'function') {\n plugin.install(app);\n }\n }\n }\n\n // Provide global values\n if (options.global?.provide) {\n for (const [key, value] of Object.entries(options.global.provide)) {\n app.provide(key, value);\n }\n }\n\n // Mount the app\n const instance = app.mount(targetEl);\n\n // Create wrapper\n const wrapper: Wrapper<T> = {\n root: (targetEl.firstElementChild as HTMLElement) || targetEl,\n vm: instance as T,\n\n unmount() {\n app.unmount();\n if (targetEl === container && container.parentNode) {\n container.parentNode.removeChild(container);\n }\n },\n\n find(selector: string) {\n return this.root.querySelector(selector);\n },\n\n findAll(selector: string) {\n return Array.from(this.root.querySelectorAll(selector));\n },\n\n props() {\n return { ...options.props };\n },\n\n async setProps(newProps: Record<string, unknown>) {\n Object.assign(options.props || {}, newProps);\n await flushPromises();\n },\n\n trigger(selector: string, event: string, detail?: unknown) {\n const el = this.find(selector);\n if (!el) {\n throw new Error(`Element not found: ${selector}`);\n }\n\n const eventObj = new CustomEvent(event, { detail, bubbles: true });\n el.dispatchEvent(eventObj);\n },\n\n text() {\n return this.root.textContent || '';\n },\n\n html() {\n return this.root.innerHTML;\n },\n };\n\n return wrapper;\n}\n\n/**\n * Mount a component and return a promise that resolves after initial render\n */\nexport async function mountAsync<T = unknown>(\n component: unknown,\n options: MountOptions = {},\n): Promise<Wrapper<T>> {\n const wrapper = mount<T>(component, options);\n await flushPromises();\n return wrapper;\n}\n\n// ============================================\n// Async Utilities\n// ============================================\n\n/**\n * Wait for all pending promises to resolve\n *\n * @example\n * ```ts\n * await flushPromises();\n * ```\n */\nexport function flushPromises(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, 0));\n}\n\n/**\n * Wait for the next tick\n *\n * @example\n * ```ts\n * await nextTick();\n * ```\n */\nexport { nextTick };\n\n/**\n * Wait for a specific condition to be true\n *\n * @example\n * ```ts\n * await waitFor(() => wrapper.text().includes('Loaded'));\n * ```\n */\nexport async function waitFor(\n condition: () => boolean,\n options: { timeout?: number; interval?: number } = {},\n): Promise<void> {\n const { timeout = 1000, interval = 50 } = options;\n\n const startTime = Date.now();\n\n while (!condition()) {\n if (Date.now() - startTime > timeout) {\n throw new Error(`waitFor timeout: condition not met within ${timeout}ms`);\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n}\n\n/**\n * Wait for a specific amount of time\n */\nexport function wait(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ============================================\n// Mock Utilities\n// ============================================\n\n/**\n * Create a mock function that tracks calls\n */\nexport function mockFn<T extends (...args: unknown[]) => unknown = (...args: unknown[]) => unknown>(\n implementation?: T,\n): MockFunction<T> {\n const calls: unknown[][] = [];\n const instances: unknown[] = [];\n let mockImplementation = implementation;\n\n const fn: MockFunction<T> = function (this: unknown, ...args: unknown[]) {\n calls.push(args);\n instances.push(this);\n if (mockImplementation) {\n return mockImplementation.apply(this, args);\n }\n return undefined;\n } as MockFunction<T>;\n\n fn.calls = calls;\n fn.instances = instances;\n\n fn.mockReturnValue = (value: unknown) => {\n mockImplementation = (() => value) as T;\n return fn;\n };\n\n fn.mockResolvedValue = (value: unknown) => {\n mockImplementation = (async () => value) as T;\n return fn;\n };\n\n fn.mockImplementation = (impl: T) => {\n mockImplementation = impl;\n return fn;\n };\n\n fn.mockClear = () => {\n calls.length = 0;\n instances.length = 0;\n return fn;\n };\n\n fn.mockReset = () => {\n fn.mockClear();\n mockImplementation = implementation;\n return fn;\n };\n\n return fn;\n}\n\nexport interface MockFunction<\n T extends (...args: unknown[]) => unknown = (...args: unknown[]) => unknown,\n> {\n (...args: Parameters<T>): ReturnType<T>;\n calls: unknown[][];\n instances: unknown[];\n mockReturnValue: (value: unknown) => MockFunction<T>;\n mockResolvedValue: (value: unknown) => MockFunction<T>;\n mockImplementation: (impl: T) => MockFunction<T>;\n mockClear: () => MockFunction<T>;\n mockReset: () => MockFunction<T>;\n}\n\n/**\n * Create a mock component for testing\n */\nexport function mockComponent(\n name: string,\n options: Partial<ComponentOptions> = {},\n): ComponentOptions {\n return defineComponent({\n name,\n setup(_props, { slots }) {\n return () =>\n (\n h as unknown as (\n tag: string,\n attrs: Record<string, unknown>,\n children?: unknown,\n ) => unknown\n )(\n 'div',\n { 'data-testid': `mock-${name.toLowerCase()}` },\n slots.default ? slots.default() : `Mock: ${name}`,\n );\n },\n ...options,\n });\n}\n\n/**\n * Spy on an object method\n */\nexport function spyOn<T extends object, K extends keyof T>(\n obj: T,\n method: K,\n implementation?: T[K],\n): MockFunction {\n const original = obj[method];\n const mock = mockFn(implementation as (...args: unknown[]) => unknown);\n\n Object.defineProperty(obj, method, {\n value: mock,\n writable: true,\n configurable: true,\n });\n\n (mock as unknown as { mockRestore: () => void }).mockRestore = () => {\n Object.defineProperty(obj, method, {\n value: original,\n writable: true,\n configurable: true,\n });\n };\n\n return mock;\n}\n\n// ============================================\n// Signal Testing Utilities\n// ============================================\n\n/**\n * Create a test signal with tracking\n */\nexport function createTestSignal<T>(initialValue: T): TestSignal<T> {\n const sig = signal(initialValue);\n const history: T[] = [initialValue];\n\n const testSignal: TestSignal<T> = {\n get value() {\n return sig();\n },\n set value(v: T) {\n sig.set(v);\n history.push(v);\n },\n history: () => [...history],\n reset: () => {\n sig.set(initialValue);\n history.length = 0;\n history.push(initialValue);\n },\n };\n\n return testSignal;\n}\n\nexport interface TestSignal<T> {\n value: T;\n history: () => T[];\n reset: () => void;\n}\n\n/**\n * Track signal changes\n */\nexport function trackSignal<T>(sig: Signal<T>): SignalTracker<T> {\n const values: { value: T; timestamp: number }[] = [];\n const stop = watch(\n () => sig(),\n (value) => {\n values.push({ value, timestamp: Date.now() });\n },\n );\n\n return {\n values: () => values.map((v) => v.value),\n timestamps: () => values.map((v) => v.timestamp),\n count: () => values.length,\n stop,\n };\n}\n\nexport interface SignalTracker<T> {\n values: () => T[];\n timestamps: () => number[];\n count: () => number;\n stop: () => void;\n}\n\n// ============================================\n// Assertion Helpers\n// ============================================\n\n/**\n * Assert that a component emits an event\n */\nexport async function assertEmits(\n wrapper: Wrapper,\n eventName: string,\n action: () => void | Promise<void>,\n timeout = 1000,\n): Promise<unknown[]> {\n const emitted: unknown[] = [];\n\n const originalEmit = (wrapper.vm as { $emit?: (event: string, ...args: unknown[]) => void })\n ?.$emit;\n if (originalEmit) {\n (wrapper.vm as { $emit: (event: string, ...args: unknown[]) => void }).$emit = (\n event: string,\n ...args: unknown[]\n ) => {\n if (event === eventName) {\n emitted.push(args.length === 1 ? args[0] : args);\n }\n originalEmit(event, ...args);\n };\n }\n\n await action();\n await flushPromises();\n\n if (emitted.length === 0) {\n throw new Error(`Event \"${eventName}\" was not emitted within ${timeout}ms`);\n }\n\n return emitted;\n}\n\n/**\n * Check if a value is reactive\n */\nexport function isReactive(_value: unknown): boolean {\n return false; // LytJS v6 uses Signals instead of Reactive Objects\n}\n\n// ============================================\n// Cleanup Utilities\n// ============================================\n\nlet cleanupCallbacks: (() => void)[] = [];\n\n/**\n * Register a cleanup callback to run after each test\n */\nexport function registerCleanup(callback: () => void): void {\n cleanupCallbacks.push(callback);\n}\n\n/**\n * Run all cleanup callbacks\n */\nexport function runCleanup(): void {\n for (const callback of cleanupCallbacks) {\n try {\n callback();\n } catch (error) {\n console.error('Cleanup error:', error);\n }\n }\n cleanupCallbacks = [];\n}\n\n/**\n * Auto-cleanup wrapper for mount\n */\nexport function mountWithCleanup<T = unknown>(\n component: unknown,\n options: MountOptions = {},\n): Wrapper<T> {\n const wrapper = mount<T>(component, options);\n registerCleanup(() => wrapper.unmount());\n return wrapper;\n}\n"]}
package/package.json CHANGED
@@ -1,53 +1,53 @@
1
- {
2
- "name": "@lytjs/test-utils",
3
- "version": "6.5.0",
4
- "description": "LytJS testing utilities for unit and integration testing",
5
- "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.mjs",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.mjs",
13
- "require": "./dist/index.cjs"
14
- },
15
- "./package.json": "./package.json"
16
- },
17
- "files": [
18
- "dist"
19
- ],
20
- "sideEffects": false,
21
- "scripts": {
22
- "build": "echo 'Skipping test-utils build for now'",
23
- "dev": "tsup --watch",
24
- "test": "vitest run",
25
- "test:watch": "vitest",
26
- "test:coverage": "vitest run --coverage",
27
- "type-check": "tsc --noEmit",
28
- "lint": "eslint \"src/**/*.ts\"",
29
- "clean": "rm -rf dist"
30
- },
31
- "dependencies": {
32
- "@lytjs/core": "^6.0.0",
33
- "@lytjs/reactivity": "^6.0.0",
34
- "@lytjs/component": "^6.0.0"
35
- },
36
- "devDependencies": {
37
- "tsup": "^8.0.0",
38
- "typescript": "^5.4.0",
39
- "vitest": "^3.0.0"
40
- },
41
- "license": "MIT",
42
- "repository": {
43
- "type": "git",
44
- "url": "https://gitee.com/lytjs/lytjs.git",
45
- "directory": "packages/tools/packages/test-utils"
46
- },
47
- "keywords": [
48
- "lytjs",
49
- "test",
50
- "testing",
51
- "utils"
52
- ]
53
- }
1
+ {
2
+ "name": "@lytjs/test-utils",
3
+ "version": "6.6.0",
4
+ "description": "LytJS testing utilities for unit and integration testing",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "sideEffects": false,
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:coverage": "vitest run --coverage",
27
+ "type-check": "tsc --noEmit",
28
+ "lint": "eslint \"src/**/*.ts\"",
29
+ "clean": "rm -rf dist"
30
+ },
31
+ "dependencies": {
32
+ "@lytjs/core": "workspace:*",
33
+ "@lytjs/reactivity": "workspace:*",
34
+ "@lytjs/component": "workspace:*"
35
+ },
36
+ "devDependencies": {
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.4.0",
39
+ "vitest": "^3.0.0"
40
+ },
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://gitee.com/lytjs/lytjs.git",
45
+ "directory": "packages/tools/packages/test-utils"
46
+ },
47
+ "keywords": [
48
+ "lytjs",
49
+ "test",
50
+ "testing",
51
+ "utils"
52
+ ]
53
+ }