|
| 1 | +import { expect, vi } from "vitest"; |
| 2 | +import type { CallbackRef } from "phoenix_live_view/assets/js/types/view_hook"; |
| 3 | +import { mockLiveSocket } from "./mock-live-socket"; |
| 4 | + |
| 5 | +export function mockHookJs() { |
| 6 | + return { |
| 7 | + exec: vi.fn(), |
| 8 | + show: vi.fn(), |
| 9 | + hide: vi.fn(), |
| 10 | + toggle: vi.fn(), |
| 11 | + addClass: vi.fn(), |
| 12 | + removeClass: vi.fn(), |
| 13 | + toggleClass: vi.fn(), |
| 14 | + transition: vi.fn(), |
| 15 | + setAttribute: vi.fn(), |
| 16 | + removeAttribute: vi.fn(), |
| 17 | + toggleAttribute: vi.fn(), |
| 18 | + push: vi.fn(), |
| 19 | + navigate: vi.fn(), |
| 20 | + patch: vi.fn(), |
| 21 | + ignoreAttributes: vi.fn(), |
| 22 | + }; |
| 23 | +} |
| 24 | + |
| 25 | +export type MockHookJs = ReturnType<typeof mockHookJs>; |
| 26 | + |
| 27 | +type BaseHookContext<E extends HTMLElement> = { |
| 28 | + el: E; |
| 29 | + pushEvent: ReturnType<typeof vi.fn>; |
| 30 | + js: () => MockHookJs; |
| 31 | + liveSocket: ReturnType<typeof mockLiveSocket>["ctx"]["liveSocket"]; |
| 32 | + handleEvent: ReturnType< |
| 33 | + typeof vi.fn<(event: string, callback: (payload: unknown) => void) => CallbackRef> |
| 34 | + >; |
| 35 | + removeHandleEvent: ReturnType<typeof vi.fn>; |
| 36 | +}; |
| 37 | + |
| 38 | +type MockHookContextOptions<Extra extends Record<string, unknown>> = { |
| 39 | + connected?: boolean; |
| 40 | + overrides?: Extra; |
| 41 | +}; |
| 42 | + |
| 43 | +export function mockHookContext< |
| 44 | + E extends HTMLElement, |
| 45 | + Extra extends Record<string, unknown> = Record<string, never>, |
| 46 | +>(el: E, opts: MockHookContextOptions<Extra> = {}) { |
| 47 | + const connected = opts.connected ?? false; |
| 48 | + const { ctx, patch, navigate } = mockLiveSocket(connected); |
| 49 | + const jsCommands = mockHookJs(); |
| 50 | + jsCommands.patch = patch; |
| 51 | + jsCommands.navigate = navigate; |
| 52 | + |
| 53 | + const base: BaseHookContext<E> = { |
| 54 | + el, |
| 55 | + pushEvent: vi.fn(), |
| 56 | + js: () => jsCommands, |
| 57 | + liveSocket: ctx.liveSocket, |
| 58 | + handleEvent: vi.fn( |
| 59 | + (event: string, callback: (payload: unknown) => void): CallbackRef => ({ |
| 60 | + event, |
| 61 | + callback, |
| 62 | + }) |
| 63 | + ), |
| 64 | + removeHandleEvent: vi.fn(), |
| 65 | + }; |
| 66 | + |
| 67 | + const hook = { ...base, ...opts.overrides } as BaseHookContext<E> & Extra; |
| 68 | + |
| 69 | + return { hook, patch, navigate, jsCommands, liveSocket: ctx.liveSocket }; |
| 70 | +} |
| 71 | + |
| 72 | +type HookLifecycle = "mounted" | "destroyed" | "updated" | "beforeUpdate"; |
| 73 | + |
| 74 | +export function callHookLifecycle( |
| 75 | + hookModule: Partial<Record<HookLifecycle, (this: never) => void>>, |
| 76 | + hook: object, |
| 77 | + lifecycle: HookLifecycle |
| 78 | +): void { |
| 79 | + const fn = hookModule[lifecycle]; |
| 80 | + expect(fn).toBeDefined(); |
| 81 | + fn!.call(hook as never); |
| 82 | +} |
| 83 | + |
| 84 | +export function callHookMounted( |
| 85 | + hookModule: Partial<Record<"mounted", (this: never) => void>>, |
| 86 | + hook: object |
| 87 | +): void { |
| 88 | + callHookLifecycle(hookModule, hook, "mounted"); |
| 89 | +} |
| 90 | + |
| 91 | +export function callHookDestroyed( |
| 92 | + hookModule: Partial<Record<"destroyed", (this: never) => void>>, |
| 93 | + hook: object |
| 94 | +): void { |
| 95 | + callHookLifecycle(hookModule, hook, "destroyed"); |
| 96 | +} |
0 commit comments