The analysis admin now reflects the OCR backend instead of the vision LLM. Backend response types are unchanged (vision fields ride along, ignored) so this is fully reversible. - Settings → Analysis: keep only the OCR-relevant knobs (workers, job timeout, max image bytes) plus a read-only "Backend: OCR (ocrs)" line; drop the endpoint/model/API-key, sampling, slicing, and prompt fieldsets. - Analysis tab: reword the lede to OCR text extraction; the page-detail modal drops the NSFW/content-warning, scene, and auto-tag sections and shows only OCR text (the per-line kind label renders only when set, since ocrs leaves it blank). - History table: remove the NSFW-only filter and the per-row NSFW tag, and stop sending the `nsfw` query param. - Metrics panel: remove the by-model table (OCR has a single engine); keep the tiles and trend charts. Add component tests asserting the history table omits the NSFW filter and never tags a row, and the metrics panel renders tiles without the by-model table. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import { render, screen, cleanup, waitFor } from '@testing-library/svelte';
|
|
|
|
// Mock the admin API so the component's onMount load() is deterministic.
|
|
const listAnalysisHistory = vi.fn();
|
|
vi.mock('$lib/api/admin', () => ({
|
|
listAnalysisHistory: (...args: unknown[]) => listAnalysisHistory(...args)
|
|
}));
|
|
|
|
import AnalysisHistoryTable from './AnalysisHistoryTable.svelte';
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
listAnalysisHistory.mockReset();
|
|
});
|
|
|
|
function row(over: Record<string, unknown> = {}) {
|
|
return {
|
|
page_id: 'p1',
|
|
page_number: 1,
|
|
chapter_id: 'c1',
|
|
chapter_number: 5,
|
|
manga_id: 'm1',
|
|
manga_title: 'Berserk',
|
|
status: 'done',
|
|
is_nsfw: true, // even when the backend reports nsfw, the OCR UI must not surface it
|
|
model: 'ocrs',
|
|
error: null,
|
|
analyzed_at: '2026-01-02T00:00:00Z',
|
|
duration_ms: 1234,
|
|
...over
|
|
};
|
|
}
|
|
|
|
function resolveOnce(rows: ReturnType<typeof row>[]) {
|
|
listAnalysisHistory.mockResolvedValue({
|
|
items: rows,
|
|
page: { total: rows.length }
|
|
});
|
|
}
|
|
|
|
describe('AnalysisHistoryTable (OCR)', () => {
|
|
it('does not render the NSFW-only filter', async () => {
|
|
resolveOnce([row()]);
|
|
render(AnalysisHistoryTable, { props: { onOpenDetail: () => {} } });
|
|
await waitFor(() => expect(listAnalysisHistory).toHaveBeenCalled());
|
|
expect(screen.queryByTestId('analysis-history-nsfw')).toBeNull();
|
|
});
|
|
|
|
it('does not pass an nsfw filter to the history query', async () => {
|
|
resolveOnce([row()]);
|
|
render(AnalysisHistoryTable, { props: { onOpenDetail: () => {} } });
|
|
await waitFor(() => expect(listAnalysisHistory).toHaveBeenCalled());
|
|
const arg = listAnalysisHistory.mock.calls[0][0] as Record<string, unknown>;
|
|
expect('nsfw' in arg).toBe(false);
|
|
});
|
|
|
|
it('never shows an NSFW tag on a row, even if the row is flagged', async () => {
|
|
resolveOnce([row({ is_nsfw: true })]);
|
|
render(AnalysisHistoryTable, { props: { onOpenDetail: () => {} } });
|
|
await waitFor(() => expect(screen.getByText(/Berserk/)).toBeTruthy());
|
|
expect(screen.queryByText(/NSFW/i)).toBeNull();
|
|
});
|
|
});
|