docs: rewrite PLAN.md and update CODEBASE.md, README.md
- Full rewrite of PLAN.md to reflect current architecture (27 REST + 3 WS endpoints, embedded frontend, automation, scheduler, health check, GoReleaser CI) - Added health.go, mods.go, scheduler.go, robfig/cron dep to CODEBASE.md - Added Gitea Actions CI/CD section to CODEBASE.md - Added conventional commits to code style section - Added missing API routes and embed/ to README.md
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import Layout from './Layout'
|
||||
|
||||
describe('Layout', () => {
|
||||
it('renders sidebar navigation', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<Layout>
|
||||
<div>Content</div>
|
||||
</Layout>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Dashboard')).toBeInTheDocument()
|
||||
expect(screen.getByText('Settings')).toBeInTheDocument()
|
||||
expect(screen.getByText('Configs')).toBeInTheDocument()
|
||||
expect(screen.getByText('Modlists')).toBeInTheDocument()
|
||||
expect(screen.getByText('Mods')).toBeInTheDocument()
|
||||
expect(screen.getByText('Logs')).toBeInTheDocument()
|
||||
expect(screen.getByText('Status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders children', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<Layout>
|
||||
<div>Test Content</div>
|
||||
</Layout>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Test Content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('highlights active route', () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/settings']}>
|
||||
<Layout>
|
||||
<div>Content</div>
|
||||
</Layout>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
const settingsLink = screen.getByText('Settings')
|
||||
expect(settingsLink).toHaveClass('text-white')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import Mods from './Mods'
|
||||
|
||||
const mockList = vi.fn()
|
||||
|
||||
vi.mock('../api/client', () => ({
|
||||
modsApi: {
|
||||
list: (...args: unknown[]) => mockList(...args),
|
||||
remove: vi.fn(),
|
||||
cleanup: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const createTestQueryClient = () =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
})
|
||||
|
||||
describe('Mods', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('renders loading state then data', async () => {
|
||||
mockList.mockResolvedValue([])
|
||||
const queryClient = createTestQueryClient()
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>
|
||||
<Mods />
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Installed Mods')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('renders search input after load', async () => {
|
||||
mockList.mockResolvedValue([])
|
||||
const queryClient = createTestQueryClient()
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>
|
||||
<Mods />
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Filter by name or ID...')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows no mods message when empty', async () => {
|
||||
mockList.mockResolvedValue([])
|
||||
const queryClient = createTestQueryClient()
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>
|
||||
<Mods />
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No mods found.')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom'
|
||||
@@ -0,0 +1,35 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { fmtSize } from './format'
|
||||
|
||||
describe('fmtSize', () => {
|
||||
it('formats 0 bytes', () => {
|
||||
expect(fmtSize(0)).toBe('0 B')
|
||||
})
|
||||
|
||||
it('formats bytes', () => {
|
||||
expect(fmtSize(500)).toBe('500 B')
|
||||
})
|
||||
|
||||
it('formats kilobytes', () => {
|
||||
expect(fmtSize(1024)).toBe('1.0 KB')
|
||||
expect(fmtSize(1536)).toBe('1.5 KB')
|
||||
})
|
||||
|
||||
it('formats megabytes', () => {
|
||||
expect(fmtSize(1048576)).toBe('1.0 MB')
|
||||
expect(fmtSize(5242880)).toBe('5.0 MB')
|
||||
})
|
||||
|
||||
it('formats gigabytes', () => {
|
||||
expect(fmtSize(1073741824)).toBe('1.0 GB')
|
||||
})
|
||||
|
||||
it('formats terabytes', () => {
|
||||
expect(fmtSize(1099511627776)).toBe('1.0 TB')
|
||||
})
|
||||
|
||||
it('handles edge case at 1024 boundary', () => {
|
||||
expect(fmtSize(1023)).toBe('1023 B')
|
||||
expect(fmtSize(1025)).toBe('1.0 KB')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
export function fmtSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 B'
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1)
|
||||
const v = bytes / Math.pow(1024, i)
|
||||
return `${v.toFixed(i > 0 ? 1 : 0)} ${units[i]}`
|
||||
}
|
||||
Reference in New Issue
Block a user