test contracts

This commit is contained in:
2026-02-26 19:40:00 +03:00
parent 626449604f
commit 2b8e20981e
35 changed files with 1811 additions and 759 deletions

View File

@@ -16,64 +16,79 @@
* @UX_STATE: Error -> Inline error with retry preserving filters.
* @UX_FEEDBACK: Filter change reloads list immediately.
* @UX_RECOVERY: Retry and clear filters actions available.
*
* @TEST_CONTRACT Page_Reports ->
* {
* required_props: {},
* optional_props: {},
* invariants: [
* "Loads reports on mount using query filters",
* "Reloads reports automatically upon filter changes",
* "Loads selected report detail on click"
* ]
* }
* @TEST_FIXTURE init_state -> {}
* @TEST_EDGE server_error -> shows error block and retry button
* @TEST_EDGE empty_filtered_list -> displays \"No results matching filters\"
* @TEST_INVARIANT pagination_and_filtering -> verifies: [init_state]
*/
import { onMount } from 'svelte';
import { t } from '$lib/i18n';
import { PageHeader } from '$lib/ui';
import { getReports, getReportDetail } from '$lib/api/reports.js';
import ReportsList from '$lib/components/reports/ReportsList.svelte';
import ReportDetailPanel from '$lib/components/reports/ReportDetailPanel.svelte';
import { onMount } from "svelte";
import { t } from "$lib/i18n";
import { PageHeader } from "$lib/ui";
import { getReports, getReportDetail } from "$lib/api/reports.js";
import ReportsList from "$lib/components/reports/ReportsList.svelte";
import ReportDetailPanel from "$lib/components/reports/ReportDetailPanel.svelte";
let loading = true;
let error = '';
let error = "";
let collection = null;
let selectedReport = null;
let selectedReportDetail = null;
let taskType = 'all';
let status = 'all';
let taskType = "all";
let status = "all";
let page = 1;
const pageSize = 20;
const TASK_TYPE_OPTIONS = [
{ value: 'all', label: $t.reports?.all_types },
{ value: 'llm_verification', label: 'LLM' },
{ value: 'backup', label: $t.nav?.backups },
{ value: 'migration', label: $t.nav?.migration },
{ value: 'documentation', label: 'Documentation' }
{ value: "all", label: $t.reports?.all_types },
{ value: "llm_verification", label: "LLM" },
{ value: "backup", label: $t.nav?.backups },
{ value: "migration", label: $t.nav?.migration },
{ value: "documentation", label: "Documentation" },
];
const STATUS_OPTIONS = [
{ value: 'all', label: $t.reports?.all_statuses },
{ value: 'success', label: 'Success' },
{ value: 'failed', label: 'Failed' },
{ value: 'in_progress', label: 'In progress' },
{ value: 'partial', label: 'Partial' }
{ value: "all", label: $t.reports?.all_statuses },
{ value: "success", label: "Success" },
{ value: "failed", label: "Failed" },
{ value: "in_progress", label: "In progress" },
{ value: "partial", label: "Partial" },
];
function buildQuery() {
return {
page,
page_size: pageSize,
task_types: taskType === 'all' ? [] : [taskType],
statuses: status === 'all' ? [] : [status],
sort_by: 'updated_at',
sort_order: 'desc'
task_types: taskType === "all" ? [] : [taskType],
statuses: status === "all" ? [] : [status],
sort_by: "updated_at",
sort_order: "desc",
};
}
async function loadReports({ silent = false } = {}) {
try {
if (!silent) loading = true;
error = '';
error = "";
collection = await getReports(buildQuery());
if (!selectedReport && collection?.items?.length) {
selectedReport = collection.items[0];
selectedReportDetail = await getReportDetail(selectedReport.report_id);
}
} catch (e) {
error = e?.message || 'Failed to load reports';
error = e?.message || "Failed to load reports";
collection = null;
} finally {
if (!silent) loading = false;
@@ -81,12 +96,12 @@
}
function hasActiveFilters() {
return taskType !== 'all' || status !== 'all';
return taskType !== "all" || status !== "all";
}
function clearFilters() {
taskType = 'all';
status = 'all';
taskType = "all";
status = "all";
page = 1;
selectedReport = null;
selectedReportDetail = null;
@@ -112,7 +127,7 @@
<div class="mx-auto w-full max-w-7xl space-y-4">
<PageHeader
title={$t.reports?.title }
title={$t.reports?.title}
subtitle={() => null}
actions={() => null}
/>
@@ -143,38 +158,52 @@
class="inline-flex items-center justify-center rounded-lg border border-slate-300 px-3 py-1.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
on:click={() => loadReports()}
>
{$t.common?.refresh }
{$t.common?.refresh}
</button>
<button
class="inline-flex items-center justify-center rounded-lg border border-slate-300 px-3 py-1.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
on:click={clearFilters}
>
{$t.reports?.clear_filters }
{$t.reports?.clear_filters}
</button>
</div>
</div>
{#if loading}
<div class="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-500 shadow-sm">
{$t.reports?.loading }
<div
class="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-500 shadow-sm"
>
{$t.reports?.loading}
</div>
{:else if error}
<div class="rounded-xl border border-red-200 bg-red-50 p-4 text-red-700 shadow-sm">
<div
class="rounded-xl border border-red-200 bg-red-50 p-4 text-red-700 shadow-sm"
>
<p>{error}</p>
<button class="mt-2 inline-flex items-center justify-center rounded-lg border border-red-300 px-3 py-1 text-sm font-medium text-red-700 transition-colors hover:bg-red-100" on:click={() => loadReports()}>
{$t.reports?.retry_load || $t.common?.retry }
<button
class="mt-2 inline-flex items-center justify-center rounded-lg border border-red-300 px-3 py-1 text-sm font-medium text-red-700 transition-colors hover:bg-red-100"
on:click={() => loadReports()}
>
{$t.reports?.retry_load || $t.common?.retry}
</button>
</div>
{:else if !collection || collection.total === 0}
<div class="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-500 shadow-sm">
{$t.reports?.empty }
<div
class="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-500 shadow-sm"
>
{$t.reports?.empty}
</div>
{:else if collection.items.length === 0 && hasActiveFilters()}
<div class="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-500 shadow-sm">
<p>{$t.reports?.filtered_empty }</p>
<button class="mt-2 inline-flex items-center justify-center rounded-lg border border-slate-300 px-3 py-1 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50" on:click={clearFilters}>
{$t.reports?.clear_filters }
<div
class="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-500 shadow-sm"
>
<p>{$t.reports?.filtered_empty}</p>
<button
class="mt-2 inline-flex items-center justify-center rounded-lg border border-slate-300 px-3 py-1 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
on:click={clearFilters}
>
{$t.reports?.clear_filters}
</button>
</div>
{:else}
@@ -191,4 +220,4 @@
{/if}
</div>
<!-- [/DEF:UnifiedReportsPage:Component] -->
<!-- [/DEF:UnifiedReportsPage:Component] -->