mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
fix(desktop): route typed local html in browser panel
Tested: - cd desktop && bun run test -- src/components/browser/BrowserAddressBar.test.tsx src/components/browser/BrowserSurface.test.tsx --run --reporter=verbose --pool=forks --maxWorkers=1 --minWorkers=1 - bun run check:desktop
This commit is contained in:
parent
b70f25e62b
commit
f9575b9749
@ -48,6 +48,20 @@ describe('BrowserAddressBar', () => {
|
||||
expect(onNavigate).toHaveBeenNthCalledWith(2, 'https://example.com')
|
||||
})
|
||||
|
||||
it('preserves typed local html paths for the browser surface to route', () => {
|
||||
const onNavigate = vi.fn()
|
||||
render(<BrowserAddressBar {...baseProps} url="" onNavigate={onNavigate} />)
|
||||
const input = screen.getByRole('textbox')
|
||||
|
||||
fireEvent.change(input, { target: { value: '/private/tmp/report.html' } })
|
||||
fireEvent.submit(input.closest('form')!)
|
||||
fireEvent.change(input, { target: { value: 'out/index.html' } })
|
||||
fireEvent.submit(input.closest('form')!)
|
||||
|
||||
expect(onNavigate).toHaveBeenNthCalledWith(1, '/private/tmp/report.html')
|
||||
expect(onNavigate).toHaveBeenNthCalledWith(2, 'out/index.html')
|
||||
})
|
||||
|
||||
it('preserves explicit URL schemes', () => {
|
||||
const onNavigate = vi.fn()
|
||||
render(<BrowserAddressBar {...baseProps} url="" onNavigate={onNavigate} />)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { ArrowLeft, ArrowRight, Loader2, RotateCw } from 'lucide-react'
|
||||
import { isHtmlFilePath } from '../../lib/htmlPreviewPolicy'
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
@ -58,6 +59,7 @@ export function normalizeBrowserAddress(input: string): string {
|
||||
const value = input.trim()
|
||||
if (!value) return ''
|
||||
if (/^[a-z][a-z\d+\-.]*:\/\//i.test(value) || /^(about|data|file):/i.test(value)) return value
|
||||
if (isHtmlFilePath(value)) return value
|
||||
if (/^(localhost|127(?:\.\d{1,3}){3}|\[::1\]|::1)(?::\d+)?(?:[/?#].*)?$/i.test(value)) {
|
||||
return `http://${value}`
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ vi.mock('../../lib/previewBridge', () => ({ previewBridge: bridge }))
|
||||
vi.mock('@tauri-apps/api/event', () => ({ listen: () => Promise.resolve(() => {}) }))
|
||||
|
||||
import { BrowserSurface } from './BrowserSurface'
|
||||
import { getDefaultBaseUrl, setBaseUrl } from '../../api/client'
|
||||
import { useBrowserPanelStore } from '../../stores/browserPanelStore'
|
||||
import { useWorkspacePanelStore } from '../../stores/workspacePanelStore'
|
||||
import { useOverlayStore } from '../../stores/overlayStore'
|
||||
@ -28,6 +29,7 @@ afterEach(() => {
|
||||
// browserPanelStore.open() now also opens the unified workbench; keep it isolated.
|
||||
useWorkspacePanelStore.setState(useWorkspacePanelStore.getInitialState(), true)
|
||||
useOverlayStore.setState(useOverlayStore.getInitialState(), true)
|
||||
setBaseUrl(getDefaultBaseUrl())
|
||||
})
|
||||
|
||||
describe('BrowserSurface', () => {
|
||||
@ -76,6 +78,27 @@ describe('BrowserSurface', () => {
|
||||
expect(bridge.navigate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('opens a typed file URL for local html through the local-file preview route', async () => {
|
||||
vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(null, { status: 200 }))
|
||||
setBaseUrl('http://127.0.0.1:8787')
|
||||
useBrowserPanelStore.getState().ensureBlank('s1')
|
||||
render(<BrowserSurface sessionId="s1" />)
|
||||
|
||||
const input = screen.getByRole('textbox')
|
||||
fireEvent.change(input, { target: { value: 'file:///private/tmp/report.html' } })
|
||||
fireEvent.submit(input.closest('form')!)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(bridge.open).toHaveBeenCalledWith(
|
||||
'http://127.0.0.1:8787/local-file/private/tmp/report.html',
|
||||
expect.objectContaining({ width: expect.any(Number) }),
|
||||
)
|
||||
})
|
||||
expect(useBrowserPanelStore.getState().bySession['s1']!.url).toBe(
|
||||
'http://127.0.0.1:8787/local-file/private/tmp/report.html',
|
||||
)
|
||||
})
|
||||
|
||||
it('navigating via address bar calls store + bridge', () => {
|
||||
useBrowserPanelStore.getState().open('s1', 'http://localhost:5173/')
|
||||
render(<BrowserSurface sessionId="s1" />)
|
||||
|
||||
@ -2,7 +2,9 @@ import { useEffect, useLayoutEffect, useRef } from 'react'
|
||||
import { Camera, Loader2, MousePointer2 } from 'lucide-react'
|
||||
import { BrowserAddressBar } from './BrowserAddressBar'
|
||||
import { computeWebviewBounds } from './computeWebviewBounds'
|
||||
import { isLoopbackHostname } from '../../lib/desktopRuntime'
|
||||
import { getServerBaseUrl, isLoopbackHostname } from '../../lib/desktopRuntime'
|
||||
import { classifyPreviewLink } from '../../lib/previewLinkRouter'
|
||||
import { isAbsoluteLocalPath, localFileUrl, previewFsUrl } from '../../lib/handlePreviewLink'
|
||||
import { previewBridge } from '../../lib/previewBridge'
|
||||
import { subscribePreviewEvents } from '../../lib/previewEvents'
|
||||
import { useBrowserPanelStore } from '../../stores/browserPanelStore'
|
||||
@ -38,6 +40,21 @@ async function waitForLocalPreview(url: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBrowserNavigationUrl(input: string, sessionId: string): string {
|
||||
const value = input.trim()
|
||||
if (!value) return ''
|
||||
|
||||
const classified = classifyPreviewLink(value)
|
||||
if (classified.kind === 'browser-file' && classified.path) {
|
||||
const serverBaseUrl = getServerBaseUrl()
|
||||
return isAbsoluteLocalPath(classified.path)
|
||||
? localFileUrl(serverBaseUrl, classified.path)
|
||||
: previewFsUrl(serverBaseUrl, sessionId, classified.path)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
export function BrowserSurface({ sessionId }: { sessionId: string }) {
|
||||
const hostRef = useRef<HTMLDivElement>(null)
|
||||
const loadSeqRef = useRef(0)
|
||||
@ -126,7 +143,8 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
|
||||
|
||||
if (!session) return null
|
||||
|
||||
const openOrNavigate = (url: string) => {
|
||||
const openOrNavigate = (inputUrl: string) => {
|
||||
const url = resolveBrowserNavigationUrl(inputUrl, sessionId)
|
||||
if (!url) return
|
||||
const current = useBrowserPanelStore.getState().bySession[sessionId]
|
||||
store.navigate(sessionId, url)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user