mirror of
https://github.com/oxynote/oxynote.git
synced 2026-10-18 03:19:19 +02:00
166 lines
5.0 KiB
TypeScript
166 lines
5.0 KiB
TypeScript
import { mockNuxtImport } from "@nuxt/test-utils/runtime"
|
|
import { beforeEach, describe, it, vi } from "vitest"
|
|
import { nextTick, ref, toValue } from "vue"
|
|
import plugin from "./websocket.client"
|
|
|
|
interface WsControlArgs {
|
|
wsFailFn: () => void
|
|
authenticated: unknown
|
|
}
|
|
|
|
const {
|
|
useAuthSessionMock,
|
|
useWebSocketStateMock,
|
|
useWebSocketStateStoreMock,
|
|
} = vi.hoisted(() => {
|
|
// the default implementations keep the app bootstrap alive: the real
|
|
// plugin instance created during nuxt init consumes these before any
|
|
// test arranges its own values
|
|
return {
|
|
useAuthSessionMock: vi.fn((): any => ({
|
|
fetchAuthSession: {
|
|
refresh: () => Promise.resolve({ data: undefined }),
|
|
state: ref({ data: undefined }),
|
|
},
|
|
fetchOrganization: {
|
|
refresh: () => Promise.resolve({ data: undefined }),
|
|
},
|
|
})),
|
|
useWebSocketStateMock: vi.fn((_opts: WsControlArgs): any => ({
|
|
openConn: () => undefined,
|
|
closeConn: () => undefined,
|
|
})),
|
|
useWebSocketStateStoreMock: vi.fn((): any => ({ init: () => undefined })),
|
|
}
|
|
})
|
|
|
|
mockNuxtImport("useAuthSession", () => useAuthSessionMock)
|
|
mockNuxtImport("useWebSocketState", () => useWebSocketStateMock)
|
|
mockNuxtImport("useWebSocketStateStore", () => useWebSocketStateStoreMock)
|
|
|
|
const ACTIVE_SESSION = { id: "s1", activeOrganizationId: "org1" }
|
|
|
|
// a signed-out response carries no data object at all, so that is the
|
|
// shape the plugin sees rather than a null session inside one
|
|
function arrange(sessionValue: object | null) {
|
|
const session = ref<{ data: { data: { session: object } | null } }>({
|
|
data: { data: sessionValue ? { session: sessionValue } : null },
|
|
})
|
|
const refresh = vi.fn().mockResolvedValue(session.value)
|
|
useAuthSessionMock.mockReturnValue({
|
|
fetchAuthSession: { refresh, state: session },
|
|
})
|
|
|
|
const wsControl = { openConn: vi.fn(), closeConn: vi.fn() }
|
|
useWebSocketStateMock.mockReturnValue(wsControl)
|
|
|
|
const init = vi.fn()
|
|
useWebSocketStateStoreMock.mockReturnValue({ init })
|
|
|
|
return { session, refresh, wsControl, init }
|
|
}
|
|
|
|
// the tests arrange shared module-level mocks (mockNuxtImport singletons),
|
|
// so they cannot interleave
|
|
describe("websocket.client", { concurrent: false }, () => {
|
|
// restoreMocks does not touch hand-made vi.fn() singletons in vitest 4
|
|
// — reset the module-level mocks explicitly
|
|
beforeEach(() => {
|
|
useAuthSessionMock.mockReset()
|
|
useWebSocketStateMock.mockReset()
|
|
useWebSocketStateStoreMock.mockReset()
|
|
})
|
|
|
|
it("provides the websocket control and initializes the state store", async ({
|
|
expect,
|
|
}) => {
|
|
const { refresh, wsControl, init } = arrange(null)
|
|
|
|
// the plugin type is a union with the void setup variant; this
|
|
// object-form setup resolves to its provide block
|
|
const result = (await plugin(useNuxtApp())) as unknown as {
|
|
provide: { wsControl: unknown }
|
|
}
|
|
|
|
expect(result.provide.wsControl).toBe(wsControl)
|
|
expect(init).toHaveBeenCalledTimes(1)
|
|
expect(refresh).toHaveBeenCalledTimes(1)
|
|
expect(wsControl.openConn).toHaveBeenCalledTimes(0)
|
|
expect(wsControl.closeConn).toHaveBeenCalledTimes(0)
|
|
})
|
|
|
|
it("opens the connection when the session becomes authenticated", async ({
|
|
expect,
|
|
}) => {
|
|
const { session, wsControl } = arrange(null)
|
|
await plugin(useNuxtApp())
|
|
|
|
session.value.data.data = { session: ACTIVE_SESSION }
|
|
await nextTick()
|
|
|
|
expect(wsControl.openConn).toHaveBeenCalledTimes(1)
|
|
expect(wsControl.closeConn).toHaveBeenCalledTimes(0)
|
|
})
|
|
|
|
it("keeps the connection closed until the session has an active organization", async ({
|
|
expect,
|
|
}) => {
|
|
const { session, wsControl } = arrange({ id: "s1" })
|
|
await plugin(useNuxtApp())
|
|
|
|
expect(wsControl.openConn).toHaveBeenCalledTimes(0)
|
|
|
|
session.value.data.data = { session: ACTIVE_SESSION }
|
|
await nextTick()
|
|
|
|
expect(wsControl.openConn).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it("opens the connection immediately when already authenticated", async ({
|
|
expect,
|
|
}) => {
|
|
const { wsControl } = arrange(ACTIVE_SESSION)
|
|
|
|
await plugin(useNuxtApp())
|
|
|
|
expect(wsControl.openConn).toHaveBeenCalledTimes(1)
|
|
expect(wsControl.closeConn).toHaveBeenCalledTimes(0)
|
|
})
|
|
|
|
it("closes the connection when the session is lost", async ({ expect }) => {
|
|
const { session, wsControl } = arrange(ACTIVE_SESSION)
|
|
await plugin(useNuxtApp())
|
|
|
|
session.value.data.data = null
|
|
await nextTick()
|
|
|
|
expect(wsControl.closeConn).toHaveBeenCalledTimes(1)
|
|
expect(wsControl.openConn).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it("closes the connection when the window unloads", async ({ expect }) => {
|
|
const { wsControl } = arrange(ACTIVE_SESSION)
|
|
await plugin(useNuxtApp())
|
|
|
|
window.dispatchEvent(new Event("beforeunload"))
|
|
|
|
expect(wsControl.closeConn).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it("reports websocket failures to the console", async ({ expect }) => {
|
|
arrange(null)
|
|
const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined)
|
|
|
|
await plugin(useNuxtApp())
|
|
|
|
const opts = useWebSocketStateMock.mock.calls[0]?.[0]
|
|
expect(toValue(opts?.authenticated)).toBe(false)
|
|
|
|
opts?.wsFailFn()
|
|
|
|
expect(logSpy).toHaveBeenCalledExactlyOnceWith(
|
|
"websocket connection failed",
|
|
)
|
|
})
|
|
})
|