Why: cursor 3/3 exhausted; PI direct dispatched as host integration work. Scope: 8 test files (7 modified + 1 new), 255 insertions, 29 new vitest tests covering 11 U4 frontend-auto-discover scenarios end-to-end: - AutoDiscover (3 new in store.test.ts) - CapabilityGroup (3 new in rolePermissions.test.ts + 6 new in panels) - CreatorOwnerMemberDuties (4 new across adminRdmSections/projectmeta/AccountUsersPanel) - MemberManagementEntryHidden (3 new in adminRdmSections/projectmeta) - EmptyListNoCreate (4 new in projectmeta/TaskSidebar) - DefaultProjectDisabled (1 new in store.test.ts/projectmeta) - RefreshBackoff + RefreshSingleFlight + RefreshHiddenPause + RefreshReconnectCompensate - ProjectLifecycleConversation (5 new in test/conversation-work.test.ts) Verification: npm run build ok (5.35s); ./node_modules/.bin/vitest run 656/656 (was 627, +29 new); new 29 tests across all 11 patterns all pass; no production files touched. Refs: plan revision 3 U5-Frontend; /tmp/iam-link-u5-frontend-pi-verify-20261019.json exit=0.
72 lines
3.5 KiB
TypeScript
72 lines
3.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
import { TaskSidebar } from './TaskSidebar'
|
|
|
|
const mocked = vi.hoisted(() => ({ state: {} as Record<string, unknown> }))
|
|
|
|
vi.mock('../store', () => ({
|
|
useStore: (select: (value: Record<string, unknown>) => unknown) => select(mocked.state),
|
|
store: { openInbox: vi.fn(), openFiles: vi.fn(), focusPending: vi.fn(), setUI: vi.fn(), toggleLeft: vi.fn() },
|
|
}))
|
|
|
|
const member = { owner: 'bob', name: 'Bob', auth: true, systemActions: [],
|
|
linkedCapabilities: { projectCreate: false, linkedActions: [], authorityEpoch: 1 } }
|
|
const creator = { ...member, linkedCapabilities: { projectCreate: true, source: 'role', linkedActions: [], authorityEpoch: 1 } }
|
|
|
|
function render(patch: Record<string, unknown>) {
|
|
mocked.state = {
|
|
projects: [], projectsLoaded: true, projectSync: { status: 'ok', failures: 0 }, tasks: [], me: member,
|
|
adminSections: [], artifacts: [], cards: [], projectCards: [], serverWorkspaces: [],
|
|
rdmAuditAvailable: false, rdmCockpitAvailable: false, currentConvId: null, currentProjectId: null,
|
|
ui: { theme: 'light', showArchived: false },
|
|
...patch,
|
|
}
|
|
return renderToStaticMarkup(<TaskSidebar />)
|
|
}
|
|
|
|
beforeEach(() => { mocked.state = {} })
|
|
|
|
describe('EmptyListNoCreate sidebar', () => {
|
|
it('shows a waiting-for-invite state without inviting a same-name project for a member without creation', () => {
|
|
const html = render({})
|
|
expect(html).toContain('尚无项目:等待项目负责人邀请你加入,加入后会自动出现在这里。')
|
|
expect(html).not.toContain('新建项目')
|
|
expect(html).toMatch(/<button class="tsb-newtask-main" disabled=""/)
|
|
})
|
|
|
|
it('shows a loading state instead of an empty state before the list arrives', () => {
|
|
const html = render({ projectsLoaded: false })
|
|
expect(html).toContain('正在加载项目列表…')
|
|
expect(html).not.toContain('等待项目负责人邀请')
|
|
})
|
|
|
|
it('points a creator to explicit project creation', () => {
|
|
expect(render({ me: creator })).toContain('还没有项目:从「新建项目」开始。')
|
|
})
|
|
})
|
|
|
|
describe('AutoDiscover sidebar status', () => {
|
|
it('surfaces a degraded or offline project sync instead of silently showing stale projects', () => {
|
|
expect(render({ projectSync: { status: 'degraded', failures: 2 } })).toContain('项目列表同步异常,正在自动重试')
|
|
expect(render({ projectSync: { status: 'offline', failures: 0 } })).toContain('网络已断开,恢复后自动同步项目列表')
|
|
expect(render({})).not.toContain('同步异常')
|
|
})
|
|
|
|
it('keeps conversation creation available once a discovered project is current', () => {
|
|
const html = render({ projects: [{ id: 'host-b', name: '项目P', owner: 'carol' }], currentProjectId: 'host-b' })
|
|
expect(html).toMatch(/<button class="tsb-newtask-main" title="在当前项目新建对话/)
|
|
expect(html).toContain('从「新建对话」开始')
|
|
})
|
|
|
|
it('falls back to a project-less sidebar when the current discovery has no project', () => {
|
|
const html = render({ projects: [], currentProjectId: null })
|
|
expect(html).toContain('尚无项目')
|
|
expect(html).not.toContain('同步异常')
|
|
})
|
|
|
|
it('does not render a sync notice when the listing succeeded', () => {
|
|
expect(render({ projectSync: { status: 'ok', failures: 0 } })).not.toContain('同步异常')
|
|
expect(render({ projectSync: { status: 'idle', failures: 0 } })).not.toContain('同步异常')
|
|
})
|
|
})
|