| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- // @vitest-environment jsdom
- import { afterEach, describe, expect, it, vi } from 'vitest'
- import type { TabId } from '@deepseek-ai/dsh-client-ui-dockkit'
- import { BrowserController, createBrowserControllers } from '../src/client/browser/BrowserController.ts'
- import { createBrowserStore } from '../src/client/browser/store.ts'
- const TAB = 'tab' as TabId
- const APP = 'https://dsh.example'
- const lifetimes = new Set<AbortController>()
- function lifetime(): AbortController {
- const controller = new AbortController()
- lifetimes.add(controller)
- return controller
- }
- afterEach(() => {
- for (const controller of lifetimes) controller.abort()
- lifetimes.clear()
- localStorage.clear()
- vi.restoreAllMocks()
- })
- describe('BrowserController', () => {
- it('ignores navigation commands after its tab occurrence ends', () => {
- const store = createBrowserStore().create('browser-controller-ended-test')
- const tabLifetime = lifetime()
- const controller = new BrowserController({
- tabId: TAB, signal: tabLifetime.signal, applicationOrigin: APP, actions: store.actions,
- })
- tabLifetime.abort()
- controller.loadUrl('https://ignored.example/')
- controller.goBack()
- controller.goForward()
- controller.reload()
- controller.frame.reportLoaded(1)
- expect(store.getSnapshot().byTab[TAB]).toBeUndefined()
- })
- it('owns the four navigation commands and frame load state', () => {
- const store = createBrowserStore().create('browser-controller-test')
- const face = createBrowserControllers(store.actions)
- const tabLifetime = lifetime()
- face.mount(TAB, tabLifetime.signal, APP)
- const frame = face.keyedHooks.browserFrame(TAB)!
- face.mount(TAB, tabLifetime.signal, APP)
- expect(face.keyedHooks.browserFrame(TAB)).toBe(frame)
- face.goBack(TAB)
- face.goForward(TAB)
- face.reload(TAB)
- face.loadUrl(TAB, 'https://example.test/one')
- const first = frame.getSnapshot().document!
- expect(first.src).toBe('https://example.test/one')
- face.reportLoaded(TAB, first.revision - 1)
- face.reportLoaded(TAB, first.revision)
- expect(store.getSnapshot().byTab[TAB]?.navigation.status).toBe('known')
- face.reportLoaded(TAB, first.revision)
- expect(store.getSnapshot().byTab[TAB]?.navigation.status).toBe('unknown')
- face.goBack(TAB)
- face.goForward(TAB)
- expect(store.getSnapshot().byTab[TAB]?.navigation.status).toBe('unknown')
- face.loadUrl(TAB, 'https://example.test/two')
- face.goBack(TAB)
- expect(frame.getSnapshot().document?.target.url).toBe('https://example.test/one')
- face.goForward(TAB)
- expect(frame.getSnapshot().document?.target.url).toBe('https://example.test/two')
- const beforeReload = store.getSnapshot().byTab[TAB]!.request!.revision
- face.reload(TAB)
- expect(store.getSnapshot().byTab[TAB]?.request?.revision).toBe(beforeReload + 1)
- face.loadUrl(TAB, 'https://example.test/two')
- expect(store.getSnapshot().byTab[TAB]?.request?.revision).toBe(beforeReload + 2)
- tabLifetime.abort()
- expect(store.getSnapshot().byTab[TAB]).toBeUndefined()
- face.reportLoaded(TAB, first.revision)
- face.loadUrl(TAB, 'https://ignored.example/')
- face.goBack(TAB)
- face.goForward(TAB)
- face.reload(TAB)
- })
- it('does not let an old occurrence delete a replacement controller', () => {
- const store = createBrowserStore().create('browser-controller-replacement-test')
- const face = createBrowserControllers(store.actions)
- const first = lifetime()
- const second = lifetime()
- face.mount(TAB, first.signal, APP)
- const original = face.keyedHooks.browserFrame(TAB)
- face.mount(TAB, second.signal, APP)
- const replacement = face.keyedHooks.browserFrame(TAB)
- expect(replacement).not.toBe(original)
- first.abort()
- expect(store.getSnapshot().byTab[TAB]).toBeUndefined()
- face.loadUrl(TAB, 'https://example.test/')
- expect(store.getSnapshot().byTab[TAB]).toBeDefined()
- face.mount(TAB, second.signal, APP)
- expect(face.keyedHooks.browserFrame(TAB)).toBe(replacement)
- })
- it('loads loopback under sandbox and reloads it across sandbox changes', () => {
- const store = createBrowserStore().create('browser-controller-loopback-test')
- const face = createBrowserControllers(store.actions)
- const tabLifetime = lifetime()
- face.mount(TAB, tabLifetime.signal, APP)
- const frame = face.keyedHooks.browserFrame(TAB)!
- face.loadUrl(TAB, 'http://localhost:5173/app')
- expect(frame.getSnapshot()).toMatchObject({
- sandboxed: true,
- document: { src: 'http://localhost:5173/app' },
- })
- const beforeToggle = store.getSnapshot().byTab[TAB]!.request!.revision
- face.toggleSandbox(TAB)
- expect(frame.getSnapshot().sandboxed).toBe(false)
- expect(frame.getSnapshot().document?.src).toBe('http://localhost:5173/app')
- expect(store.getSnapshot().byTab[TAB]?.request?.revision).toBe(beforeToggle + 1)
- face.toggleSandbox(TAB)
- expect(frame.getSnapshot()).toMatchObject({
- sandboxed: true,
- document: { src: 'http://localhost:5173/app' },
- })
- expect(store.getSnapshot().byTab[TAB]?.request?.revision).toBe(beforeToggle + 2)
- })
- it('loads public HTTP without changing the sandbox policy', () => {
- const store = createBrowserStore().create('browser-controller-http-test')
- const face = createBrowserControllers(store.actions)
- const tabLifetime = lifetime()
- face.mount(TAB, tabLifetime.signal, APP)
- face.loadUrl(TAB, 'http://example.test/path')
- expect(face.keyedHooks.browserFrame(TAB)?.getSnapshot()).toMatchObject({
- sandboxed: true,
- document: { src: 'http://example.test/path' },
- })
- })
- })
|