feat: Introduce and enforce test contract annotations for critical modules and update coverage tracking.
This commit is contained in:
@@ -5,6 +5,13 @@
|
||||
// @LAYER: UI (Tests)
|
||||
// @RELATION: VERIFIES -> frontend/src/components/TaskLogViewer.svelte
|
||||
// @INVARIANT: Duplicate logs are never appended. Polling only active for in-progress tasks.
|
||||
// @TEST_CONTRACT: TaskLogViewerPropsAndLogStream -> RenderedLogTimeline
|
||||
// @TEST_SCENARIO: historical_and_realtime_merge -> Historical logs render and realtime logs append without duplication.
|
||||
// @TEST_FIXTURE: valid_viewer -> INLINE_JSON
|
||||
// @TEST_EDGE: no_task_id -> Null taskId does not trigger fetch.
|
||||
// @TEST_EDGE: fetch_failure -> Network failure renders recoverable error state with retry action.
|
||||
// @TEST_EDGE: duplicate_realtime_entry -> Existing log is not duplicated when repeated in realtime stream.
|
||||
// @TEST_INVARIANT: no_duplicate_log_rows -> VERIFIED_BY: [historical_and_realtime_merge, duplicate_realtime_entry]
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/svelte';
|
||||
@@ -15,6 +22,8 @@ vi.mock('../../services/taskService.js', () => ({
|
||||
getTaskLogs: vi.fn()
|
||||
}));
|
||||
|
||||
const getTaskLogsMock = vi.mocked(getTaskLogs);
|
||||
|
||||
vi.mock('../../lib/i18n', () => ({
|
||||
t: {
|
||||
subscribe: (fn) => {
|
||||
@@ -39,13 +48,13 @@ describe('TaskLogViewer Component', () => {
|
||||
});
|
||||
|
||||
it('renders loading state initially', () => {
|
||||
getTaskLogs.mockResolvedValue([]);
|
||||
getTaskLogsMock.mockResolvedValue([]);
|
||||
render(TaskLogViewer, { inline: true, taskId: 'task-123' });
|
||||
expect(screen.getByText('Loading...')).toBeDefined();
|
||||
});
|
||||
|
||||
it('fetches and displays historical logs', async () => {
|
||||
getTaskLogs.mockResolvedValue([
|
||||
getTaskLogsMock.mockResolvedValue([
|
||||
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Historical log entry' }
|
||||
]);
|
||||
|
||||
@@ -59,7 +68,7 @@ describe('TaskLogViewer Component', () => {
|
||||
});
|
||||
|
||||
it('displays error message on fetch failure', async () => {
|
||||
getTaskLogs.mockRejectedValue(new Error('Network error fetching logs'));
|
||||
getTaskLogsMock.mockRejectedValue(new Error('Network error fetching logs'));
|
||||
|
||||
render(TaskLogViewer, { inline: true, taskId: 'task-123' });
|
||||
|
||||
@@ -70,7 +79,7 @@ describe('TaskLogViewer Component', () => {
|
||||
});
|
||||
|
||||
it('appends real-time logs passed as props', async () => {
|
||||
getTaskLogs.mockResolvedValue([
|
||||
getTaskLogsMock.mockResolvedValue([
|
||||
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Historical log entry' }
|
||||
]);
|
||||
|
||||
@@ -99,7 +108,7 @@ describe('TaskLogViewer Component', () => {
|
||||
});
|
||||
|
||||
it('deduplicates real-time logs that are already in historical logs', async () => {
|
||||
getTaskLogs.mockResolvedValue([
|
||||
getTaskLogsMock.mockResolvedValue([
|
||||
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Duplicate log entry' }
|
||||
]);
|
||||
|
||||
@@ -132,7 +141,7 @@ describe('TaskLogViewer Component', () => {
|
||||
|
||||
// @TEST_FIXTURE valid_viewer
|
||||
it('fetches and displays historical logs in modal mode under valid_viewer fixture', async () => {
|
||||
getTaskLogs.mockResolvedValue([
|
||||
getTaskLogsMock.mockResolvedValue([
|
||||
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Modal log entry' }
|
||||
]);
|
||||
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
// @LAYER: UI Tests
|
||||
// @RELATION: VERIFIES -> frontend/src/lib/components/assistant/AssistantChatPanel.svelte
|
||||
// @INVARIANT: Critical assistant UX states and action hooks remain present in component source.
|
||||
// @TEST_CONTRACT: AssistantChatSourceArtifacts -> ContractAssertions
|
||||
// @TEST_SCENARIO: assistant_contract_and_i18n_intact -> Component semantic/UX anchors and locale keys stay consistent.
|
||||
// @TEST_FIXTURE: assistant_locales_en_ru -> file:src/lib/i18n/locales/en.json + file:src/lib/i18n/locales/ru.json
|
||||
// @TEST_EDGE: missing_component_anchor -> Missing DEF/UX tags fails contract assertion.
|
||||
// @TEST_EDGE: missing_action_hook -> Missing confirm/cancel/open_task hooks fails integration assertion.
|
||||
// @TEST_EDGE: missing_locale_key -> Missing assistant locale key in en/ru fails dictionary assertion.
|
||||
// @TEST_INVARIANT: assistant_ux_contract_visible -> VERIFIED_BY: [assistant_contract_and_i18n_intact]
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
// @LAYER: UI
|
||||
// @RELATION: VERIFIES -> ../ReportCard.svelte
|
||||
// @INVARIANT: Each test asserts at least one observable UX contract outcome.
|
||||
// @TEST_CONTRACT: ReportCardInputProps -> ObservableUXOutput
|
||||
// @TEST_SCENARIO: ready_state_shows_summary_status_type -> Ready state renders summary/status/type labels.
|
||||
// @TEST_FIXTURE: valid_report_card -> INLINE_JSON
|
||||
// @TEST_EDGE: empty_report_object -> Missing fields use placeholders and fallback labels.
|
||||
// @TEST_EDGE: random_status -> Unknown status is rendered without crashing.
|
||||
// @TEST_EDGE: missing_optional_fields -> Partial report keeps component interactive and emits select.
|
||||
// @TEST_INVARIANT: report_card_state_is_observable -> VERIFIED_BY: [ready_state_shows_summary_status_type, empty_report_object, random_status]
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/svelte';
|
||||
@@ -39,7 +46,7 @@ describe('ReportCard UX Contract', () => {
|
||||
|
||||
// @UX_STATE: Ready -> Card displays summary/status/type.
|
||||
it('should display summary, status and type in Ready state', () => {
|
||||
render(ReportCard, { report: mockReport });
|
||||
render(ReportCard, { report: mockReport, onselect: vi.fn() });
|
||||
expect(screen.getByText(mockReport.summary)).toBeDefined();
|
||||
// mockReport.status is "success", getStatusLabel(status) returns $t.reports?.status_success
|
||||
expect(screen.getByText('Success')).toBeDefined();
|
||||
@@ -61,7 +68,7 @@ describe('ReportCard UX Contract', () => {
|
||||
// @UX_RECOVERY: Missing fields are rendered with explicit placeholder text.
|
||||
it('should render placeholders for missing fields', () => {
|
||||
const partialReport = { report_id: 'partial-1' };
|
||||
render(ReportCard, { report: partialReport });
|
||||
render(ReportCard, { report: partialReport, onselect: vi.fn() });
|
||||
|
||||
// Check placeholders (using text from mocked $t)
|
||||
const placeholders = screen.getAllByText('Not provided');
|
||||
@@ -79,7 +86,7 @@ describe('ReportCard UX Contract', () => {
|
||||
summary: "Test Summary",
|
||||
updated_at: "2024-01-01"
|
||||
};
|
||||
render(ReportCard, { report: validReportCard });
|
||||
render(ReportCard, { report: validReportCard, onselect: vi.fn() });
|
||||
|
||||
expect(screen.getByText('Test Summary')).toBeDefined();
|
||||
expect(screen.getByText('Success')).toBeDefined();
|
||||
@@ -87,14 +94,14 @@ describe('ReportCard UX Contract', () => {
|
||||
|
||||
// @TEST_EDGE empty_report_object
|
||||
it('should handle completely empty report object gracefully', () => {
|
||||
render(ReportCard, { report: {} });
|
||||
render(ReportCard, { report: {}, onselect: vi.fn() });
|
||||
const placeholders = screen.getAllByText('Not provided');
|
||||
expect(placeholders.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// @TEST_EDGE random_status
|
||||
it('should render random status directly if no translation matches', () => {
|
||||
render(ReportCard, { report: { status: "unknown_status_code" } });
|
||||
render(ReportCard, { report: { status: "unknown_status_code" }, onselect: vi.fn() });
|
||||
expect(screen.getByText('unknown_status_code')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user