|
| 1 | +import type { ConnectionMeta } from 'devframe/types' |
| 2 | +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' |
| 3 | +import { getDevframeRpcClient } from './rpc' |
| 4 | + |
| 5 | +const CONNECTION_META_KEY = '__DEVFRAME_CONNECTION_META__' |
| 6 | + |
| 7 | +// Minimal fake WebSocket: records the URL it was dialed with (all this suite |
| 8 | +// needs) and never opens, so the trust handshake stays pending. |
| 9 | +class FakeWebSocket { |
| 10 | + static instances: FakeWebSocket[] = [] |
| 11 | + constructor(public url: string) { |
| 12 | + FakeWebSocket.instances.push(this) |
| 13 | + } |
| 14 | + |
| 15 | + addEventListener(): void {} |
| 16 | + removeEventListener(): void {} |
| 17 | + send(): void {} |
| 18 | + close(): void {} |
| 19 | +} |
| 20 | + |
| 21 | +class FakeBroadcastChannel { |
| 22 | + onmessage: ((e: any) => void) | null = null |
| 23 | + postMessage(): void {} |
| 24 | + close(): void {} |
| 25 | +} |
| 26 | + |
| 27 | +function lastWsUrl(): string { |
| 28 | + return FakeWebSocket.instances.at(-1)!.url |
| 29 | +} |
| 30 | + |
| 31 | +describe('getDevframeRpcClient — connection meta base', () => { |
| 32 | + beforeEach(() => { |
| 33 | + FakeWebSocket.instances = [] |
| 34 | + vi.stubGlobal('WebSocket', FakeWebSocket) |
| 35 | + vi.stubGlobal('BroadcastChannel', FakeBroadcastChannel) |
| 36 | + vi.stubGlobal('navigator', { userAgent: 'test' }) |
| 37 | + vi.stubGlobal('location', { |
| 38 | + protocol: 'http:', |
| 39 | + host: 'localhost:5173', |
| 40 | + hostname: 'localhost', |
| 41 | + // The SPA under test is mounted at /__foo/. |
| 42 | + href: 'http://localhost:5173/__foo/index.html', |
| 43 | + origin: 'http://localhost:5173', |
| 44 | + }) |
| 45 | + delete (globalThis as any)[CONNECTION_META_KEY] |
| 46 | + }) |
| 47 | + |
| 48 | + afterEach(() => { |
| 49 | + vi.restoreAllMocks() |
| 50 | + vi.unstubAllGlobals() |
| 51 | + delete (globalThis as any)[CONNECTION_META_KEY] |
| 52 | + }) |
| 53 | + |
| 54 | + it('publishes the meta annotated with the absolute base it resolved from', async () => { |
| 55 | + const served: ConnectionMeta = { backend: 'websocket', websocket: { path: '__ws' } } |
| 56 | + vi.stubGlobal('fetch', vi.fn(async () => ({ json: async () => served }) as any)) |
| 57 | + |
| 58 | + await getDevframeRpcClient({ baseURL: '/__foo/', otpParam: false }) |
| 59 | + |
| 60 | + const published = (globalThis as any)[CONNECTION_META_KEY] as ConnectionMeta |
| 61 | + expect(published.baseUrl).toBe('http://localhost:5173/__foo/__connection.json') |
| 62 | + // The publisher itself dials the endpoint relative to its own base. |
| 63 | + expect(lastWsUrl()).toBe('ws://localhost:5173/__foo/__ws') |
| 64 | + }) |
| 65 | + |
| 66 | + it('inherits the publisher base so a child at another base dials the shared endpoint', async () => { |
| 67 | + // A same-origin parent already published its meta, carrying the base it was |
| 68 | + // resolved against (`/__devtools/`), not this child's base (`/__foo/`). |
| 69 | + ;(globalThis as any)[CONNECTION_META_KEY] = { |
| 70 | + backend: 'websocket', |
| 71 | + websocket: { path: '__ws' }, |
| 72 | + baseUrl: 'http://localhost:5173/__devtools/__connection.json', |
| 73 | + } satisfies ConnectionMeta |
| 74 | + const fetchSpy = vi.fn() |
| 75 | + vi.stubGlobal('fetch', fetchSpy) |
| 76 | + |
| 77 | + const rpc = await getDevframeRpcClient({ baseURL: '/__foo/', otpParam: false }) |
| 78 | + |
| 79 | + // No fetch — the meta came off the window. |
| 80 | + expect(fetchSpy).not.toHaveBeenCalled() |
| 81 | + // Resolved against the inherited base, not the child's own `/__foo/`. |
| 82 | + expect(lastWsUrl()).toBe('ws://localhost:5173/__devtools/__ws') |
| 83 | + expect(rpc.connectionMeta.baseUrl).toBe('http://localhost:5173/__devtools/__connection.json') |
| 84 | + }) |
| 85 | + |
| 86 | + it('ignores a window baseUrl when connection meta is passed explicitly', async () => { |
| 87 | + ;(globalThis as any)[CONNECTION_META_KEY] = { |
| 88 | + backend: 'websocket', |
| 89 | + websocket: { path: '__ws' }, |
| 90 | + baseUrl: 'http://localhost:5173/__devtools/__connection.json', |
| 91 | + } satisfies ConnectionMeta |
| 92 | + |
| 93 | + await getDevframeRpcClient({ |
| 94 | + baseURL: '/__foo/', |
| 95 | + otpParam: false, |
| 96 | + connectionMeta: { backend: 'websocket', websocket: { path: '__ws' } }, |
| 97 | + }) |
| 98 | + |
| 99 | + // An explicit meta resolves against the client's own base. |
| 100 | + expect(lastWsUrl()).toBe('ws://localhost:5173/__foo/__ws') |
| 101 | + }) |
| 102 | +}) |
0 commit comments