8fb6c2bd69
Scope decision: previews for a browser that is not on the Host machine are not supported. With that settled, host.openPath answers the supported case completely — a file:// document in a real browser has full page capabilities and no reach into /api — and the HTTP serving this branch had built answered only the unsupported one. Removed: the /f route and its listener, the workspace-file URL shape, ApiProxy.workspaceRootOf, ConnectionHandle.fileUrl, and the port published into the index page. Kept, and finished: - the produced-files row a turn ends with, derived from mutation locations; - the path link now reads as a link at rest, not only on hover — the reported "I can't open what it made" was this, sitting on a working capability; - the Host opener prefers the default BROWSER for .html/.htm/.xhtml/.svg, so a developer who binds .html to an editor still gets a rendered page (macOS via the LaunchServices https handler, Linux via $BROWSER, every failure falling back to the default application). The retired designs and their measurements stay in the Agent Note, including why same-origin serving was unsafe and why the sandbox that fixed it broke the pages invisibly.
67 lines
2.6 KiB
TypeScript
67 lines
2.6 KiB
TypeScript
/**
|
|
* Connection plugin browser-half apply: ctx.connection handle mounting, mode
|
|
* selection off the page URL, and the single-consumer stream-loop ownership.
|
|
*/
|
|
import { Context } from 'cordis'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { apply, type ConnectionHandle } from '../src/client/index.ts'
|
|
import { FixtureApiClient } from '../src/client/fixture.ts'
|
|
import { WebApiClient } from '../src/client/web-api-client.ts'
|
|
|
|
type Win = { location?: { search: string } }
|
|
|
|
afterEach(() => {
|
|
delete (globalThis as Win).location
|
|
})
|
|
|
|
async function mount(): Promise<ConnectionHandle> {
|
|
const ctx = new Context()
|
|
await ctx.plugin({ apply, inject: [] })
|
|
const handle = ctx.get('connection') as ConnectionHandle | undefined
|
|
if (handle === undefined) throw new Error('ctx.connection not provided')
|
|
return handle
|
|
}
|
|
|
|
describe('connection client apply', () => {
|
|
it('mounts ctx.connection with the real client when no ?fixture switch is present', async () => {
|
|
;(globalThis as Win).location = { search: '' }
|
|
const handle = await mount()
|
|
expect(handle.api).toBeInstanceOf(WebApiClient)
|
|
})
|
|
|
|
it('selects the fixture client under ?fixture (and with no location at all stays real)', async () => {
|
|
;(globalThis as Win).location = { search: '?fixture' }
|
|
expect((await mount()).api).toBeInstanceOf(FixtureApiClient)
|
|
delete (globalThis as Win).location
|
|
expect((await mount()).api).toBeInstanceOf(WebApiClient)
|
|
})
|
|
|
|
it('start() hands out one loop, rejects a second consumer, and stop() aborts the streams', async () => {
|
|
;(globalThis as Win).location = { search: '?fixture' }
|
|
const handle = await mount()
|
|
// config omitted: the `config ?? {}` default arm is part of the surface.
|
|
const loop = handle.start({})
|
|
expect(() => handle.start({})).toThrow(/already owned by another consumer/)
|
|
loop.stop() // teardown must not throw; the fixture streams abort quietly
|
|
})
|
|
|
|
it('WebApiClient carries requests over globalThis.fetch', async () => {
|
|
;(globalThis as Win).location = { search: '' }
|
|
const handle = await mount()
|
|
const original = globalThis.fetch
|
|
const seen: string[] = []
|
|
globalThis.fetch = (input: URL | RequestInfo) => {
|
|
seen.push(typeof input === 'string' ? input : input instanceof URL ? input.href : input.url)
|
|
return Promise.resolve(new Response('{}', { status: 200 }))
|
|
}
|
|
try {
|
|
// Schema rejection is fine — the transport hop is the assertion.
|
|
await (handle.api as WebApiClient).host.describe({}).catch(() => undefined)
|
|
} finally {
|
|
globalThis.fetch = original
|
|
}
|
|
expect(seen.some(u => u.includes('/api/'))).toBe(true)
|
|
})
|
|
|
|
})
|