agent-ecosystem/test/features/member-work-sync/renderer/memberWorkSyncRenderer.test.tsx

136 lines
3.9 KiB
TypeScript

import React, { act } from 'react';
import { createRoot } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
MemberWorkSyncBadge,
MemberWorkSyncDetails,
MemberWorkSyncStatusPanel,
useMemberWorkSyncStatus,
} from '@features/member-work-sync/renderer';
import type { MemberWorkSyncStatus } from '@features/member-work-sync/contracts';
const apiMocks = vi.hoisted(() => ({
getStatus: vi.fn(),
}));
vi.mock('@renderer/api', () => ({
api: {
memberWorkSync: {
getStatus: apiMocks.getStatus,
},
},
isElectronMode: () => true,
}));
function makeStatus(overrides: Partial<MemberWorkSyncStatus> = {}): MemberWorkSyncStatus {
return {
teamName: 'team-a',
memberName: 'bob',
state: 'needs_sync',
agenda: {
teamName: 'team-a',
memberName: 'bob',
generatedAt: '2026-04-29T00:00:00.000Z',
fingerprint: 'agenda:v1:abcdef1234567890',
items: [
{
taskId: 'task-1',
displayId: '11111111',
subject: 'Ship UI',
kind: 'work',
assignee: 'bob',
priority: 'normal',
reason: 'owned_pending_task',
evidence: { status: 'pending', owner: 'bob' },
},
],
diagnostics: [],
},
shadow: {
reconciledBy: 'queue',
wouldNudge: true,
fingerprintChanged: false,
},
evaluatedAt: '2026-04-29T00:00:00.000Z',
diagnostics: ['developer_only'],
...overrides,
};
}
describe('member work sync renderer', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
});
afterEach(() => {
document.body.innerHTML = '';
vi.unstubAllGlobals();
});
it('loads read-only status through the renderer hook', async () => {
apiMocks.getStatus.mockResolvedValue(makeStatus());
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
function Harness(): React.ReactElement {
const state = useMemberWorkSyncStatus({ teamName: 'team-a', memberName: 'bob' });
return React.createElement('div', null, state.loading ? 'Loading' : state.viewModel.label);
}
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
expect(apiMocks.getStatus).toHaveBeenCalledWith({ teamName: 'team-a', memberName: 'bob' });
expect(host.textContent).toContain('Needs sync');
});
it('renders neutral diagnostics without exposing raw diagnostics by default', async () => {
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
await act(async () => {
root.render(
React.createElement(
'div',
null,
React.createElement(MemberWorkSyncBadge, { status: makeStatus() }),
React.createElement(MemberWorkSyncDetails, { status: makeStatus() })
)
);
});
expect(host.textContent).toContain('Needs sync');
expect(host.textContent).toContain('Shadow would nudge');
expect(host.textContent).toContain('11111111');
expect(host.textContent).not.toContain('developer_only');
});
it('renders the status panel through the read-only API hook', async () => {
apiMocks.getStatus.mockResolvedValue(makeStatus());
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
await act(async () => {
root.render(
React.createElement(MemberWorkSyncStatusPanel, {
teamName: 'team-a',
memberName: 'bob',
})
);
await Promise.resolve();
});
expect(host.textContent).toContain('Member work sync');
expect(host.textContent).toContain('Needs sync');
expect(host.textContent).toContain('Shadow would nudge');
expect(apiMocks.getStatus).toHaveBeenCalledWith({ teamName: 'team-a', memberName: 'bob' });
});
});