Files
traefik/webui/src/pages/hub-demo/HubDashboard.spec.tsx
2025-10-27 17:40:06 +01:00

205 lines
6.8 KiB
TypeScript

import { waitFor } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import HubDashboard, { resetCache } from './HubDashboard'
import verifySignature from './workers/scriptVerification'
import { renderWithProviders } from 'utils/test'
vi.mock('./workers/scriptVerification', () => ({
default: vi.fn(),
}))
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom')
return {
...actual,
useParams: vi.fn(() => ({ id: 'test-id' })),
}
})
vi.mock('hooks/use-theme', () => ({
useIsDarkMode: vi.fn(() => false),
useTheme: vi.fn(() => ({
selectedTheme: 'light',
appliedTheme: 'light',
setTheme: vi.fn(),
})),
}))
describe('HubDashboard demo', () => {
const mockVerifyScriptSignature = vi.mocked(verifySignature)
let mockCreateObjectURL: ReturnType<typeof vi.fn>
beforeEach(() => {
vi.clearAllMocks()
// Mock URL.createObjectURL
mockCreateObjectURL = vi.fn(() => 'blob:mock-url')
globalThis.URL.createObjectURL = mockCreateObjectURL
})
afterEach(() => {
vi.restoreAllMocks()
})
describe('without cache', () => {
beforeEach(() => {
// Reset cache before each test suites
resetCache()
})
it('should render loading state during script verification', async () => {
const mockScriptContent = new ArrayBuffer(100)
mockVerifyScriptSignature.mockImplementation(
() =>
new Promise((resolve) =>
setTimeout(() => resolve({ verified: true, scriptContent: mockScriptContent }), 100),
),
)
const { getByTestId } = renderWithProviders(<HubDashboard path="dashboard" />, {
route: '/hub-dashboard',
})
expect(getByTestId('loading')).toBeInTheDocument()
await waitFor(() => {
expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
})
})
it('should render the custom web component when signature is verified', async () => {
const mockScriptContent = new ArrayBuffer(100)
mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
const { container } = renderWithProviders(<HubDashboard path="dashboard" />, {
route: '/hub-dashboard',
})
await waitFor(() => {
expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
})
const hubComponent = container.querySelector('hub-ui-demo-app')
expect(hubComponent).toBeInTheDocument()
expect(hubComponent?.getAttribute('path')).toBe('dashboard')
expect(hubComponent?.getAttribute('baseurl')).toBe('#/hub-dashboard')
expect(hubComponent?.getAttribute('theme')).toBe('light')
})
it('should render error state when signature verification fails', async () => {
mockVerifyScriptSignature.mockResolvedValue({ verified: false })
const { container } = renderWithProviders(<HubDashboard path="dashboard" />)
await waitFor(() => {
expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
})
expect(container.textContent).toContain("Oops! We couldn't load the demo content")
const errorImage = container.querySelector('img[src="/img/gopher-something-went-wrong.png"]')
expect(errorImage).toBeInTheDocument()
const links = container.querySelectorAll('a')
const websiteLink = Array.from(links).find((link) => link.href.includes('traefik.io/traefik-hub'))
const docLink = Array.from(links).find((link) => link.href.includes('doc.traefik.io/traefik-hub'))
expect(websiteLink).toBeInTheDocument()
expect(docLink).toBeInTheDocument()
})
it('should render error state when verification throws an error', async () => {
mockVerifyScriptSignature.mockRejectedValue(new Error('Network error'))
const { container } = renderWithProviders(<HubDashboard path="dashboard" />)
await waitFor(() => {
expect(container.textContent).toContain("Oops! We couldn't load the demo content")
})
})
it('should call verifyScriptSignature with correct parameters', async () => {
const mockScriptContent = new ArrayBuffer(100)
mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
renderWithProviders(<HubDashboard path="dashboard" />)
await waitFor(() => {
expect(mockVerifyScriptSignature).toHaveBeenCalledWith(
'https://assets.traefik.io/hub-ui-demo.js',
'https://assets.traefik.io/hub-ui-demo.js.sig',
)
})
})
it('should set theme attribute based on dark mode', async () => {
const mockScriptContent = new ArrayBuffer(100)
mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
const { container } = renderWithProviders(<HubDashboard path="dashboard" />)
await waitFor(() => {
expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
})
const hubComponent = container.querySelector('hub-ui-demo-app')
expect(hubComponent?.getAttribute('theme')).toMatch(/light|dark/)
})
it('should handle path with :id parameter correctly', async () => {
const mockScriptContent = new ArrayBuffer(100)
mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
const { container } = renderWithProviders(<HubDashboard path="gateways:id" />)
await waitFor(() => {
expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
})
const hubComponent = container.querySelector('hub-ui-demo-app')
expect(hubComponent).toBeInTheDocument()
expect(hubComponent?.getAttribute('path')).toBe('gateways/test-id')
})
})
describe('with cache', () => {
beforeEach(() => {
resetCache()
})
it('should use cached blob URL without calling verifySignature again', async () => {
const mockScriptContent = new ArrayBuffer(100)
mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent })
// First render
const { container: firstContainer, unmount: firstUnmount } = renderWithProviders(
<HubDashboard path="dashboard" />,
)
await waitFor(() => {
expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1)
})
const firstHubComponent = firstContainer.querySelector('hub-ui-demo-app')
expect(firstHubComponent).toBeInTheDocument()
firstUnmount()
mockVerifyScriptSignature.mockClear()
// Second render - should use cache
const { container: secondContainer } = renderWithProviders(<HubDashboard path="dashboard" />)
await waitFor(() => {
const secondHubComponent = secondContainer.querySelector('hub-ui-demo-app')
expect(secondHubComponent).toBeInTheDocument()
})
expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(0)
})
})
})