mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-07 01:14:14 +00:00
4077546492
* feat: acknowledge Compose Doctor preflight findings per stack Add node-scoped preflight acknowledgements with read-time filtering. Supports four expiry modes and activeStatus for banner, tab dot, and readiness. * fix: align preflight acknowledge UI with design system Use Combobox, modal chrome, mono fields, and non-destructive clear confirm. * fix: update test mocks to match new preflight field names The preflight-acknowledgements feature renamed status-\>activeStatus and highestSeverity-\>activeHighestSeverity in the preflight report shape. The corresponding test mocks in three files still used the old field names, causing 6 test failures across backend and frontend. - backend: update-guard-service mock now passes activeStatus - frontend PreflightPanel: Report interface and report() helper now include activeStatus, activeHighestSeverity, activeCount, acknowledgedCount - frontend StackAnatomyPanel doctor: mock API response now includes activeHighestSeverity and activeStatus
86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
/**
|
|
* Covers the capability-gated Doctor tab and its severity dot in
|
|
* StackAnatomyPanel when the active node advertises compose-doctor. The
|
|
* capability-off case (tab hidden, no badge fetch) is covered in
|
|
* StackAnatomyPanel.test.tsx.
|
|
*/
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
|
|
vi.mock('@/lib/api', () => ({ apiFetch: vi.fn() }));
|
|
vi.mock('./stack/StackActivityTimeline', () => ({ StackActivityTimeline: () => <div /> }));
|
|
vi.mock('@/context/NodeContext', () => ({ useNodes: () => ({ activeNode: { id: 1 }, hasCapability: () => true }) }));
|
|
|
|
import { apiFetch } from '@/lib/api';
|
|
import StackAnatomyPanel from './StackAnatomyPanel';
|
|
|
|
let badgeSeverity: string | null = 'blocker';
|
|
|
|
function jsonRes(body: unknown, ok = true) {
|
|
return { ok, status: ok ? 200 : 404, json: async () => body, text: async () => '' } as unknown as Response;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.mocked(apiFetch).mockReset();
|
|
vi.mocked(apiFetch).mockImplementation(async (input: RequestInfo | URL) => {
|
|
const url = String(input);
|
|
if (url.includes('/preflight')) {
|
|
return jsonRes({ stack: 'web', ranAt: 1, ranBy: 'x', renderable: true, renderError: null, status: 'high', highestSeverity: badgeSeverity, activeStatus: badgeSeverity === 'warning' ? 'warning' : 'high', activeHighestSeverity: badgeSeverity, activeCount: 0, acknowledgedCount: 0, findings: [] });
|
|
}
|
|
return jsonRes(null, false); // git-source, update-preview, scan-status
|
|
});
|
|
});
|
|
|
|
function panel() {
|
|
return (
|
|
<StackAnatomyPanel
|
|
stackName="web"
|
|
content={'services:\n web:\n image: nginx:1.25\n'}
|
|
envContent=""
|
|
selectedEnvFile=".env"
|
|
gitSourcePending={false}
|
|
onEditCompose={vi.fn()}
|
|
onOpenGitSource={vi.fn()}
|
|
onApplyUpdate={vi.fn()}
|
|
canEdit
|
|
/>
|
|
);
|
|
}
|
|
|
|
describe('StackAnatomyPanel Doctor tab (capability on)', () => {
|
|
it('renders the Doctor tab with a destructive dot for a blocker result', async () => {
|
|
badgeSeverity = 'blocker';
|
|
render(panel());
|
|
expect(await screen.findByTestId('doctor-tab')).toBeInTheDocument();
|
|
const dot = await screen.findByTestId('doctor-tab-dot');
|
|
expect(dot.className).toContain('bg-destructive');
|
|
});
|
|
|
|
it('uses the warning color for a high-risk result', async () => {
|
|
badgeSeverity = 'high';
|
|
render(panel());
|
|
const dot = await screen.findByTestId('doctor-tab-dot');
|
|
expect(dot.className).toContain('bg-warning');
|
|
});
|
|
|
|
it('shows no dot for a warning-only result', async () => {
|
|
badgeSeverity = 'warning';
|
|
render(panel());
|
|
await screen.findByTestId('doctor-tab');
|
|
await waitFor(() => expect(vi.mocked(apiFetch).mock.calls.some(([u]) => String(u).includes('/preflight'))).toBe(true));
|
|
expect(screen.queryByTestId('doctor-tab-dot')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('renders the Networking tab when the capability is present', async () => {
|
|
badgeSeverity = 'warning';
|
|
render(panel());
|
|
expect(await screen.findByTestId('networking-tab')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders the Storage tab when the capability is present', async () => {
|
|
badgeSeverity = 'warning';
|
|
render(panel());
|
|
expect(await screen.findByTestId('storage-tab')).toBeInTheDocument();
|
|
});
|
|
});
|