fix(ci): resolve E2E workflow failures and boost test coverage
E2E Workflow Fixes: Add frontend dependency installation step (missing npm ci in frontend/) Remove incorrect working-directory from backend build step Update Node.js version from v18 to v20 (dependency requirements) Backend Coverage: 84.9% → 85.0% (20+ new test functions): Access list service validation and templates Backup service error handling and edge cases Security audit logs and rule sets Auth service edge cases and token validation Certificate service upload and sync error paths Frontend Coverage: 85.06% → 85.66% (27 new tests): Tabs component accessibility and keyboard navigation Plugins page status badges and error handling SecurityHeaders CRUD operations and presets API wrappers for credentials and encryption endpoints E2E Infrastructure: Enhanced global-setup with emergency security module reset Added retry logic and verification for settings propagation Known Issues: 19 E2E tests still failing (ACL blocking security APIs - Issue #16) 7 Plugins modal UI tests failing (non-critical) To be addressed in follow-up PR Fixes #550 E2E workflow failures Related to #16 ACL implementation
This commit is contained in:
221
frontend/src/components/ui/Tabs.test.tsx
Normal file
221
frontend/src/components/ui/Tabs.test.tsx
Normal file
@@ -0,0 +1,221 @@
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from './Tabs'
|
||||
|
||||
describe('Tabs', () => {
|
||||
it('renders tabs container with proper role', () => {
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
const tablist = screen.getByRole('tablist')
|
||||
expect(tablist).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders all tabs with correct labels', () => {
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">First Tab</TabsTrigger>
|
||||
<TabsTrigger value="tab2">Second Tab</TabsTrigger>
|
||||
<TabsTrigger value="tab3">Third Tab</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
expect(screen.getByRole('tab', { name: 'First Tab' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('tab', { name: 'Second Tab' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('tab', { name: 'Third Tab' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('first tab is active by default', () => {
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
||||
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
const tab1 = screen.getByRole('tab', { name: 'Tab 1' })
|
||||
const tab2 = screen.getByRole('tab', { name: 'Tab 2' })
|
||||
|
||||
expect(tab1).toHaveAttribute('data-state', 'active')
|
||||
expect(tab2).toHaveAttribute('data-state', 'inactive')
|
||||
})
|
||||
|
||||
it('clicking tab changes active state', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
||||
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
const tab2 = screen.getByRole('tab', { name: 'Tab 2' })
|
||||
await user.click(tab2)
|
||||
|
||||
expect(tab2).toHaveAttribute('data-state', 'active')
|
||||
})
|
||||
|
||||
it('only one tab active at a time', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
||||
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
||||
<TabsTrigger value="tab3">Tab 3</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
const tab1 = screen.getByRole('tab', { name: 'Tab 1' })
|
||||
const tab2 = screen.getByRole('tab', { name: 'Tab 2' })
|
||||
const tab3 = screen.getByRole('tab', { name: 'Tab 3' })
|
||||
|
||||
// Initially tab1 is active
|
||||
expect(tab1).toHaveAttribute('data-state', 'active')
|
||||
|
||||
// Click tab2
|
||||
await user.click(tab2)
|
||||
expect(tab2).toHaveAttribute('data-state', 'active')
|
||||
expect(tab1).toHaveAttribute('data-state', 'inactive')
|
||||
expect(tab3).toHaveAttribute('data-state', 'inactive')
|
||||
|
||||
// Click tab3
|
||||
await user.click(tab3)
|
||||
expect(tab3).toHaveAttribute('data-state', 'active')
|
||||
expect(tab1).toHaveAttribute('data-state', 'inactive')
|
||||
expect(tab2).toHaveAttribute('data-state', 'inactive')
|
||||
})
|
||||
|
||||
it('disabled tab cannot be clicked', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
||||
<TabsTrigger value="tab2" disabled>Tab 2</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
const tab1 = screen.getByRole('tab', { name: 'Tab 1' })
|
||||
const tab2 = screen.getByRole('tab', { name: 'Tab 2' })
|
||||
|
||||
expect(tab2).toBeDisabled()
|
||||
await user.click(tab2)
|
||||
|
||||
// Tab 1 should still be active
|
||||
expect(tab1).toHaveAttribute('data-state', 'active')
|
||||
expect(tab2).toHaveAttribute('data-state', 'inactive')
|
||||
})
|
||||
|
||||
it('keyboard navigation with arrow keys', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
||||
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
||||
<TabsTrigger value="tab3">Tab 3</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
const tab1 = screen.getByRole('tab', { name: 'Tab 1' })
|
||||
const tab2 = screen.getByRole('tab', { name: 'Tab 2' })
|
||||
|
||||
tab1.focus()
|
||||
expect(tab1).toHaveFocus()
|
||||
|
||||
// Arrow right should move focus and activate tab2
|
||||
await user.keyboard('{ArrowRight}')
|
||||
expect(tab2).toHaveFocus()
|
||||
expect(tab2).toHaveAttribute('data-state', 'active')
|
||||
})
|
||||
|
||||
it('active tab has correct aria-selected', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
||||
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
const tab1 = screen.getByRole('tab', { name: 'Tab 1' })
|
||||
const tab2 = screen.getByRole('tab', { name: 'Tab 2' })
|
||||
|
||||
expect(tab1).toHaveAttribute('aria-selected', 'true')
|
||||
expect(tab2).toHaveAttribute('aria-selected', 'false')
|
||||
|
||||
await user.click(tab2)
|
||||
|
||||
expect(tab1).toHaveAttribute('aria-selected', 'false')
|
||||
expect(tab2).toHaveAttribute('aria-selected', 'true')
|
||||
})
|
||||
|
||||
it('tab panels show/hide based on active tab', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
||||
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="tab1" data-testid="content1">Content 1</TabsContent>
|
||||
<TabsContent value="tab2" data-testid="content2">Content 2</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
// Content 1 should be visible (active)
|
||||
const content1 = screen.getByTestId('content1')
|
||||
expect(content1).toBeInTheDocument()
|
||||
expect(content1).toHaveAttribute('data-state', 'active')
|
||||
|
||||
// Content 2 should be hidden (inactive)
|
||||
const content2 = screen.getByTestId('content2')
|
||||
expect(content2).toBeInTheDocument()
|
||||
expect(content2).toHaveAttribute('data-state', 'inactive')
|
||||
|
||||
const tab2 = screen.getByRole('tab', { name: 'Tab 2' })
|
||||
await user.click(tab2)
|
||||
|
||||
// After click, content states should swap
|
||||
expect(content1).toHaveAttribute('data-state', 'inactive')
|
||||
expect(content2).toHaveAttribute('data-state', 'active')
|
||||
})
|
||||
|
||||
it('custom className is applied', () => {
|
||||
render(
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList className="custom-list-class">
|
||||
<TabsTrigger value="tab1" className="custom-trigger-class">Tab 1</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="tab1" className="custom-content-class">Content</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
const tablist = screen.getByRole('tablist')
|
||||
const tab = screen.getByRole('tab', { name: 'Tab 1' })
|
||||
const content = screen.getByText('Content')
|
||||
|
||||
expect(tablist).toHaveClass('custom-list-class')
|
||||
expect(tab).toHaveClass('custom-trigger-class')
|
||||
expect(content).toHaveClass('custom-content-class')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user