148 lines
4.4 KiB
TypeScript
148 lines
4.4 KiB
TypeScript
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||
|
|
import { setActivePinia, createPinia } from 'pinia'
|
||
|
|
|
||
|
|
import { useAppLauncherStore } from '../appLauncher'
|
||
|
|
|
||
|
|
// Mock window.open for new-tab tests
|
||
|
|
const mockWindowOpen = vi.fn()
|
||
|
|
vi.stubGlobal('open', mockWindowOpen)
|
||
|
|
|
||
|
|
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('opens an app in the iframe overlay', () => {
|
||
|
|
const store = useAppLauncherStore()
|
||
|
|
|
||
|
|
store.open({ url: 'http://192.168.1.228:8080', title: 'Mempool' })
|
||
|
|
|
||
|
|
expect(store.isOpen).toBe(true)
|
||
|
|
expect(store.url).toBe('http://192.168.1.228:8080')
|
||
|
|
expect(store.title).toBe('Mempool')
|
||
|
|
expect(mockWindowOpen).not.toHaveBeenCalled()
|
||
|
|
})
|
||
|
|
|
||
|
|
it('opens BTCPay (port 23000) in a new tab due to X-Frame-Options', () => {
|
||
|
|
const store = useAppLauncherStore()
|
||
|
|
|
||
|
|
store.open({ url: 'http://192.168.1.228:23000', title: 'BTCPay' })
|
||
|
|
|
||
|
|
expect(store.isOpen).toBe(false)
|
||
|
|
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||
|
|
'http://192.168.1.228:23000',
|
||
|
|
'_blank',
|
||
|
|
'noopener,noreferrer',
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('opens Home Assistant (port 8123) in a new tab', () => {
|
||
|
|
const store = useAppLauncherStore()
|
||
|
|
|
||
|
|
store.open({ url: 'http://192.168.1.228:8123', title: 'Home Assistant' })
|
||
|
|
|
||
|
|
expect(store.isOpen).toBe(false)
|
||
|
|
expect(mockWindowOpen).toHaveBeenCalled()
|
||
|
|
})
|
||
|
|
|
||
|
|
it('opens Grafana (port 3000) in a new tab', () => {
|
||
|
|
const store = useAppLauncherStore()
|
||
|
|
|
||
|
|
store.open({ url: 'http://192.168.1.228:3000', title: 'Grafana' })
|
||
|
|
|
||
|
|
expect(store.isOpen).toBe(false)
|
||
|
|
expect(mockWindowOpen).toHaveBeenCalled()
|
||
|
|
})
|
||
|
|
|
||
|
|
it('opens in new tab when openInNewTab flag is set', () => {
|
||
|
|
const store = useAppLauncherStore()
|
||
|
|
|
||
|
|
store.open({ url: 'http://192.168.1.228:8080', title: 'Mempool', openInNewTab: true })
|
||
|
|
|
||
|
|
expect(store.isOpen).toBe(false)
|
||
|
|
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||
|
|
'http://192.168.1.228:8080',
|
||
|
|
'_blank',
|
||
|
|
'noopener,noreferrer',
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('rewrites URL to proxy path on HTTPS for same-host apps', () => {
|
||
|
|
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' })
|
||
|
|
|
||
|
|
expect(store.isOpen).toBe(true)
|
||
|
|
expect(store.url).toBe('https://192.168.1.228/app/filebrowser/')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('does not rewrite URL on HTTP (no mixed content)', () => {
|
||
|
|
const store = useAppLauncherStore()
|
||
|
|
|
||
|
|
store.open({ url: 'http://192.168.1.228:8083', title: 'FileBrowser' })
|
||
|
|
|
||
|
|
expect(store.url).toBe('http://192.168.1.228:8083')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('does not rewrite URL for different hosts', () => {
|
||
|
|
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:8083', title: 'Remote FileBrowser' })
|
||
|
|
|
||
|
|
// Different host — no proxy rewriting
|
||
|
|
expect(store.url).toBe('http://192.168.1.100:8083')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('close resets state', () => {
|
||
|
|
const store = useAppLauncherStore()
|
||
|
|
store.open({ url: 'http://192.168.1.228:8080', title: 'Mempool' })
|
||
|
|
|
||
|
|
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:8080', title: 'Mempool' })
|
||
|
|
store.close()
|
||
|
|
|
||
|
|
expect(store.isOpen).toBe(false)
|
||
|
|
expect(store.url).toBe('')
|
||
|
|
|
||
|
|
// requestAnimationFrame fires the focus restore callback
|
||
|
|
vi.runAllTimers()
|
||
|
|
vi.useRealTimers()
|
||
|
|
})
|
||
|
|
})
|