/** * Hooks page deep-link test — verifies: * 1. /hooks?name= preselects the matching hook by id. * 2. When the hook is a `plugin.boot:` / `plugin.shutdown:` hook, * a "Registered by" link is rendered that navigates to * /plugins?name=. */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { mount, flushPromises } from "@vue/test-utils"; import { createRouter, createMemoryHistory } from "vue-router"; import { VueQueryPlugin } from "@tanstack/vue-query"; import Hooks from "../Hooks.vue"; class FakeEventSource { onopen: (() => void) | null = null; onerror: (() => void) | null = null; onmessage: ((m: { data: string }) => void) | null = null; constructor(public readonly url: string) { queueMicrotask(() => this.onerror?.()); } close(): void {} } const pluginBootHook = { id: "h-1", name: "plugin.boot:auth", chain: 2, listeners: 1, }; const otherHook = { id: "h-2", name: "action.before:submitAssignment", chain: 1, listeners: 0, }; beforeEach(() => { (globalThis as unknown as { EventSource: typeof FakeEventSource }).EventSource = FakeEventSource; globalThis.fetch = vi.fn((url: string | URL) => { const u = String(url); if (u.includes("/__nwire/manifest.json")) { return Promise.resolve( new Response( JSON.stringify({ generatedAt: new Date().toISOString(), apps: [], modules: [], actions: [], events: [], actors: [], projections: [], queries: [], resolvers: [], routes: [], workflows: [], externalCalls: [], inboundWebhooks: [], outboxes: [], inboxes: [], crons: [], topology: { hooks: [pluginBootHook, otherHook], plugins: [] }, graph: { events: [] }, }), { status: 200 }, ), ); } return Promise.resolve(new Response("", { status: 404 })); }) as typeof fetch; }); function makeRouter() { return createRouter({ history: createMemoryHistory(), routes: [ { path: "/hooks", name: "hooks", component: Hooks }, { path: "/plugins", name: "plugins", component: { template: "
" } }, ], }); } describe("Hooks deep-links", () => { it("preselects a hook by name and shows the 'Registered by' plugin link", async () => { const router = makeRouter(); await router.push("/hooks?name=plugin.boot:auth"); const wrapper = mount(Hooks, { global: { plugins: [router, VueQueryPlugin] } }); await flushPromises(); await flushPromises(); expect(wrapper.text()).toContain("plugin.boot:auth"); expect(wrapper.find("[data-testid=plugin-link-auth]").exists()).toBe(true); }); it("does not render the plugin link for non-plugin hooks", async () => { const router = makeRouter(); await router.push("/hooks?name=action.before:submitAssignment"); const wrapper = mount(Hooks, { global: { plugins: [router, VueQueryPlugin] } }); await flushPromises(); await flushPromises(); expect(wrapper.text()).toContain("action.before:submitAssignment"); expect(wrapper.find("[data-testid^=plugin-link-]").exists()).toBe(false); }); it("clicking the plugin link navigates to /plugins?name=", async () => { const router = makeRouter(); await router.push("/hooks?name=plugin.boot:auth"); const wrapper = mount(Hooks, { global: { plugins: [router, VueQueryPlugin] } }); await flushPromises(); await flushPromises(); await wrapper.find("[data-testid=plugin-link-auth]").trigger("click"); await flushPromises(); expect(router.currentRoute.value.path).toBe("/plugins"); expect(router.currentRoute.value.query.name).toBe("auth"); }); });