Updated appLauncher tests to match current session-based routing. Fixed settings test to use h2 instead of h1. Fixed RPC client test to expect 'Session expired' on 401. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
165 lines
5.2 KiB
TypeScript
165 lines
5.2 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { setActivePinia, createPinia } from 'pinia'
|
|
|
|
// vi.hoisted runs before vi.mock hoisting
|
|
const { mockPush, mockWindowOpen } = vi.hoisted(() => ({
|
|
mockPush: vi.fn(),
|
|
mockWindowOpen: vi.fn(),
|
|
}))
|
|
|
|
// Mock vue-router
|
|
vi.mock('vue-router', () => ({
|
|
useRouter: () => ({ push: mockPush }),
|
|
}))
|
|
vi.mock('@/router', () => ({
|
|
default: { push: mockPush },
|
|
}))
|
|
|
|
vi.stubGlobal('open', mockWindowOpen)
|
|
|
|
import { useAppLauncherStore } from '../appLauncher'
|
|
|
|
describe('useAppLauncherStore', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia())
|
|
vi.clearAllMocks()
|
|
// Default to HTTP to avoid proxy rewriting
|
|
Object.defineProperty(window, 'location', {
|
|
value: { origin: 'http://192.168.1.228', protocol: 'http:', hostname: '192.168.1.228' },
|
|
writable: true,
|
|
configurable: true,
|
|
})
|
|
})
|
|
|
|
it('starts closed with empty state', () => {
|
|
const store = useAppLauncherStore()
|
|
expect(store.isOpen).toBe(false)
|
|
expect(store.url).toBe('')
|
|
expect(store.title).toBe('')
|
|
})
|
|
|
|
it('routes known port apps to full-page session', () => {
|
|
const store = useAppLauncherStore()
|
|
|
|
// Port 8083 maps to /app/filebrowser/ — should route to session
|
|
store.open({ url: 'http://192.168.1.228:8083', title: 'FileBrowser' })
|
|
|
|
// Legacy overlay should NOT open — routed to session view instead
|
|
expect(store.isOpen).toBe(false)
|
|
expect(mockPush).toHaveBeenCalledWith({ name: 'app-session', params: { appId: 'filebrowser' } })
|
|
expect(mockWindowOpen).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('routes BTCPay (port 23000) to full-page session', () => {
|
|
const store = useAppLauncherStore()
|
|
|
|
store.open({ url: 'http://192.168.1.228:23000', title: 'BTCPay' })
|
|
|
|
expect(store.isOpen).toBe(false)
|
|
expect(mockPush).toHaveBeenCalledWith({ name: 'app-session', params: { appId: 'btcpay' } })
|
|
})
|
|
|
|
it('routes Home Assistant (port 8123) to full-page session', () => {
|
|
const store = useAppLauncherStore()
|
|
|
|
store.open({ url: 'http://192.168.1.228:8123', title: 'Home Assistant' })
|
|
|
|
expect(store.isOpen).toBe(false)
|
|
expect(mockPush).toHaveBeenCalled()
|
|
})
|
|
|
|
it('routes Grafana (port 3000) to full-page session', () => {
|
|
const store = useAppLauncherStore()
|
|
|
|
store.open({ url: 'http://192.168.1.228:3000', title: 'Grafana' })
|
|
|
|
expect(store.isOpen).toBe(false)
|
|
expect(mockPush).toHaveBeenCalled()
|
|
})
|
|
|
|
it('opens in new tab when openInNewTab flag is set for unknown URL', () => {
|
|
const store = useAppLauncherStore()
|
|
|
|
// Use an unresolvable URL so it doesn't route to session
|
|
store.open({ url: 'http://192.168.1.228:9999', title: 'Unknown', openInNewTab: true })
|
|
|
|
expect(store.isOpen).toBe(false)
|
|
expect(mockWindowOpen).toHaveBeenCalledWith(
|
|
'http://192.168.1.228:9999',
|
|
'_blank',
|
|
'noopener,noreferrer',
|
|
)
|
|
})
|
|
|
|
it('routes HTTPS same-host apps via session view', () => {
|
|
Object.defineProperty(window, 'location', {
|
|
value: { origin: 'https://192.168.1.228', protocol: 'https:', hostname: '192.168.1.228' },
|
|
writable: true,
|
|
configurable: true,
|
|
})
|
|
const store = useAppLauncherStore()
|
|
|
|
store.open({ url: 'http://192.168.1.228:8083', title: 'FileBrowser' })
|
|
|
|
// Known port — routes to full-page session
|
|
expect(store.isOpen).toBe(false)
|
|
expect(mockPush).toHaveBeenCalledWith({ name: 'app-session', params: { appId: 'filebrowser' } })
|
|
})
|
|
|
|
it('opens unknown URL in iframe overlay on HTTP', () => {
|
|
const store = useAppLauncherStore()
|
|
|
|
// Unresolvable URL — falls through to iframe overlay
|
|
store.open({ url: 'http://192.168.1.228:9999', title: 'Custom App' })
|
|
|
|
expect(store.isOpen).toBe(true)
|
|
expect(store.url).toBe('http://192.168.1.228:9999')
|
|
expect(store.title).toBe('Custom App')
|
|
expect(mockWindowOpen).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('opens unknown different-host URL in iframe overlay', () => {
|
|
Object.defineProperty(window, 'location', {
|
|
value: { origin: 'https://192.168.1.228', protocol: 'https:', hostname: '192.168.1.228' },
|
|
writable: true,
|
|
configurable: true,
|
|
})
|
|
const store = useAppLauncherStore()
|
|
|
|
store.open({ url: 'http://192.168.1.100:9999', title: 'Remote App' })
|
|
|
|
// Different host, unknown port — opens in iframe overlay (no proxy rewrite)
|
|
expect(store.isOpen).toBe(true)
|
|
expect(store.url).toBe('http://192.168.1.100:9999')
|
|
})
|
|
|
|
it('close resets state', () => {
|
|
const store = useAppLauncherStore()
|
|
// Use unknown URL to trigger iframe overlay
|
|
store.open({ url: 'http://192.168.1.228:9999', title: 'Custom' })
|
|
|
|
store.close()
|
|
|
|
expect(store.isOpen).toBe(false)
|
|
expect(store.url).toBe('')
|
|
expect(store.title).toBe('')
|
|
})
|
|
|
|
it('close restores focus to previous element', async () => {
|
|
vi.useFakeTimers()
|
|
const store = useAppLauncherStore()
|
|
const mockButton = { focus: vi.fn() } as unknown as HTMLElement
|
|
Object.defineProperty(document, 'activeElement', { value: mockButton, configurable: true })
|
|
|
|
store.open({ url: 'http://192.168.1.228:9999', title: 'Custom' })
|
|
store.close()
|
|
|
|
expect(store.isOpen).toBe(false)
|
|
expect(store.url).toBe('')
|
|
|
|
// requestAnimationFrame fires the focus restore callback
|
|
vi.runAllTimers()
|
|
vi.useRealTimers()
|
|
})
|
|
})
|