126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { InstanceContext, InstanceModule } from '../app-shell.js';
|
|
import {
|
|
InstanceDetail,
|
|
type CapabilityDataSource,
|
|
type InstanceCapabilityMap,
|
|
} from './instance-detail.js';
|
|
|
|
afterEach(cleanup);
|
|
|
|
const owner: InstanceContext = {
|
|
id: 'owner',
|
|
name: 'Owner modem',
|
|
origin: 'https://owner.example/path',
|
|
status: 'online',
|
|
authentication: 'authenticated',
|
|
freshness: 'fresh',
|
|
};
|
|
|
|
const capabilities: InstanceCapabilityMap = {
|
|
overview: { state: 'supported' },
|
|
messages: { state: 'degraded', explanation: 'Message history is read-only.' },
|
|
calls: { state: 'unsupported', explanation: 'This modem has no voice support.' },
|
|
esim: { state: 'unknown', explanation: 'Capability discovery did not report eSIM.' },
|
|
};
|
|
|
|
describe('InstanceDetail', () => {
|
|
it('makes only supported modules actionable and explains every other capability state', () => {
|
|
render(
|
|
<InstanceDetail
|
|
instanceId="owner"
|
|
module="overview"
|
|
instance={owner}
|
|
capabilities={capabilities}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByRole('link', { name: 'Overview' }).getAttribute('href')).toBe(
|
|
'/instances/owner/overview',
|
|
);
|
|
expect(screen.queryByRole('link', { name: 'Messages' })).toBeNull();
|
|
expect(screen.getByText('Message history is read-only.')).toBeTruthy();
|
|
expect(screen.getByText('This modem has no voice support.')).toBeTruthy();
|
|
expect(screen.getByText('Capability discovery did not report eSIM.')).toBeTruthy();
|
|
expect(screen.getAllByText('Capability state is unknown.').length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('never renders or loads context when the direct-route owner does not match', () => {
|
|
const load = vi.fn<CapabilityDataSource['load']>();
|
|
render(
|
|
<InstanceDetail
|
|
instanceId="someone-else"
|
|
module="messages"
|
|
instance={owner}
|
|
capabilityDataSource={{ load }}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByText('Owner modem')).toBeNull();
|
|
expect(screen.queryByRole('navigation', { name: 'Instance modules' })).toBeNull();
|
|
expect(screen.getByText(/Instance context is unavailable/i)).toBeTruthy();
|
|
expect(load).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('aborts the prior owner load and fences stale results to the latest owner', async () => {
|
|
const pending = new Map<
|
|
string,
|
|
{ resolve: (value: InstanceCapabilityMap) => void; signal: AbortSignal }
|
|
>();
|
|
const dataSource: CapabilityDataSource = {
|
|
load(instanceId, signal) {
|
|
return new Promise((resolve) => pending.set(instanceId, { resolve, signal }));
|
|
},
|
|
};
|
|
const second: InstanceContext = { ...owner, id: 'second', name: 'Second modem' };
|
|
const { rerender } = render(
|
|
<InstanceDetail
|
|
instanceId="owner"
|
|
module="overview"
|
|
instance={owner}
|
|
capabilityDataSource={dataSource}
|
|
/>,
|
|
);
|
|
await waitFor(() => expect(pending.has('owner')).toBe(true));
|
|
|
|
rerender(
|
|
<InstanceDetail
|
|
instanceId="second"
|
|
module="overview"
|
|
instance={second}
|
|
capabilityDataSource={dataSource}
|
|
/>,
|
|
);
|
|
await waitFor(() => expect(pending.has('second')).toBe(true));
|
|
expect(pending.get('owner')?.signal.aborted).toBe(true);
|
|
|
|
pending.get('second')?.resolve({ overview: { state: 'supported' } });
|
|
expect(await screen.findByRole('link', { name: 'Overview' })).toBeTruthy();
|
|
pending
|
|
.get('owner')
|
|
?.resolve({ overview: { state: 'unsupported', explanation: 'Stale owner result' } });
|
|
await waitFor(() => expect(screen.queryByText('Stale owner result')).toBeNull());
|
|
expect(screen.getByText('Second modem')).toBeTruthy();
|
|
});
|
|
|
|
it.each<[InstanceModule, string]>([
|
|
['cellular', 'Cellular'],
|
|
['device-network', 'Device Network'],
|
|
['automation', 'Automation'],
|
|
['ota', 'OTA'],
|
|
])('uses the existing module contract for %s', (module, label) => {
|
|
render(
|
|
<InstanceDetail
|
|
instanceId="owner"
|
|
module={module}
|
|
instance={owner}
|
|
capabilities={{ [module]: { state: 'supported' } }}
|
|
/>,
|
|
);
|
|
expect(screen.getByRole('heading', { name: label })).toBeTruthy();
|
|
});
|
|
});
|