|
|
@@ -3,20 +3,22 @@
|
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
|
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
|
|
-import type { TeamView } from '@deepseek-ai/dsh-agent-team-remotes/types'
|
|
|
-import type { TeamTaskView as TeamTask } from '@deepseek-ai/dsh-agent-team-remotes/types'
|
|
|
+import type { TeamTaskId, TeamTaskView as TeamTask, TeamView } from '@deepseek-ai/dsh-team/client'
|
|
|
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
|
|
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
|
|
import {
|
|
|
TeamAction, type TeamActionInjected, type TeamActionProps, type TeamActionResult,
|
|
|
+ type TeamTaskActionResult,
|
|
|
} from '../src/client/TeamAction.tsx'
|
|
|
import { zh } from '../src/client/locales.ts'
|
|
|
|
|
|
afterEach(cleanup)
|
|
|
|
|
|
const SESSION = 'lead' as SessionId
|
|
|
+const TASK_1 = 'task-1' as TeamTaskId
|
|
|
+const TASK_2 = 'task-2' as TeamTaskId
|
|
|
const task: TeamTask = {
|
|
|
- id: 'task-1',
|
|
|
+ id: TASK_1,
|
|
|
revision: 1,
|
|
|
subject: 'Implement runtime',
|
|
|
description: 'Build the Team runtime',
|
|
|
@@ -31,7 +33,7 @@ const view: TeamView = {
|
|
|
members: [
|
|
|
{ id: SESSION, name: 'lead', role: 'lead', status: 'idle', model: 'model-a', diagnostics: [] },
|
|
|
{
|
|
|
- id: 'worker-id',
|
|
|
+ id: 'worker-id' as SessionId,
|
|
|
name: 'worker',
|
|
|
role: 'teammate',
|
|
|
status: 'inactive',
|
|
|
@@ -42,6 +44,28 @@ const view: TeamView = {
|
|
|
tasks: [task],
|
|
|
}
|
|
|
|
|
|
+function taskSuccess(value: TeamTask): TeamTaskActionResult {
|
|
|
+ return { ok: true, value: { ok: true, value } }
|
|
|
+}
|
|
|
+
|
|
|
+function taskConflict(message: string): TeamTaskActionResult {
|
|
|
+ return {
|
|
|
+ ok: true,
|
|
|
+ value: { ok: false, error: { code: 'team-task-conflict', message } },
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function taskRejected(message: string): TeamTaskActionResult {
|
|
|
+ return {
|
|
|
+ ok: true,
|
|
|
+ value: { ok: false, error: { code: 'team-rejected', message } },
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function remoteFailure(message: string): { ok: false; error: { code: 'internal'; message: string; details: {} } } {
|
|
|
+ return { ok: false, error: { code: 'internal', message, details: {} } }
|
|
|
+}
|
|
|
+
|
|
|
function props(actions: TeamActionInjected, sessionId: SessionId = SESSION): TeamActionProps {
|
|
|
return {
|
|
|
sessionId,
|
|
|
@@ -53,8 +77,11 @@ function props(actions: TeamActionInjected, sessionId: SessionId = SESSION): Tea
|
|
|
function actions(overrides: Partial<TeamActionInjected> = {}): TeamActionInjected {
|
|
|
return {
|
|
|
load: () => Promise.resolve({ ok: true, value: view }),
|
|
|
- createTask: () => Promise.resolve({ ok: true, value: { ...task, id: 'task-2', subject: 'New task' } }),
|
|
|
- updateTask: () => Promise.resolve({ ok: true, value: { ...task, revision: 2 } }),
|
|
|
+ createTask: () => Promise.resolve({ ok: true, value: { ...task, id: TASK_2, subject: 'New task' } }),
|
|
|
+ updateTask: () => Promise.resolve({
|
|
|
+ ok: true,
|
|
|
+ value: { ok: true, value: { ...task, revision: 2 } },
|
|
|
+ }),
|
|
|
openTeammate: () => Promise.resolve(),
|
|
|
...overrides,
|
|
|
}
|
|
|
@@ -67,7 +94,7 @@ describe('TeamAction', () => {
|
|
|
const nextView: TeamView = {
|
|
|
...view,
|
|
|
members: [{ id: nextSession, name: 'lead', role: 'lead', status: 'idle', diagnostics: [] }],
|
|
|
- tasks: [{ ...task, id: 'task-next', subject: 'Next session task' }],
|
|
|
+ tasks: [{ ...task, id: 'task-next' as TeamTaskId, subject: 'Next session task' }],
|
|
|
}
|
|
|
const load = vi.fn((sessionId: SessionId) => sessionId === SESSION
|
|
|
? firstLoad.promise
|
|
|
@@ -104,7 +131,7 @@ describe('TeamAction', () => {
|
|
|
const newer = Promise.withResolvers<TeamActionResult<TeamView>>()
|
|
|
const newestView = {
|
|
|
...view,
|
|
|
- tasks: [{ ...task, id: 'newest-task', subject: 'Newest task' }],
|
|
|
+ tasks: [{ ...task, id: 'newest-task' as TeamTaskId, subject: 'Newest task' }],
|
|
|
}
|
|
|
const load = vi.fn()
|
|
|
.mockResolvedValueOnce({ ok: true, value: view })
|
|
|
@@ -131,10 +158,9 @@ describe('TeamAction', () => {
|
|
|
const load = vi.fn()
|
|
|
.mockResolvedValueOnce({ ok: true, value: view })
|
|
|
.mockImplementationOnce(() => stale.promise)
|
|
|
- const updateTask = vi.fn(() => Promise.resolve({
|
|
|
- ok: true as const,
|
|
|
- value: { ...task, revision: 2, status: 'completed' as const },
|
|
|
- }))
|
|
|
+ const updateTask = vi.fn(() => Promise.resolve(
|
|
|
+ taskSuccess({ ...task, revision: 2, status: 'completed' }),
|
|
|
+ ))
|
|
|
render(<TeamAction {...props(actions({ load, updateTask }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
@@ -233,7 +259,7 @@ describe('TeamAction', () => {
|
|
|
default:
|
|
|
throw new Error(`unexpected action ${input.action}`)
|
|
|
}
|
|
|
- return Promise.resolve({ ok: true as const, value: current })
|
|
|
+ return Promise.resolve(taskSuccess(current))
|
|
|
})
|
|
|
render(<TeamAction {...props(actions({ updateTask }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
@@ -283,11 +309,7 @@ describe('TeamAction', () => {
|
|
|
const load = vi.fn()
|
|
|
.mockResolvedValueOnce({ ok: true, value: view })
|
|
|
.mockResolvedValueOnce({ ok: true, value: { ...view, tasks: [{ ...task, revision: 2 }] } })
|
|
|
- const updateTask = vi.fn(() => Promise.resolve({
|
|
|
- ok: false as const,
|
|
|
- error: 'stale (team-task-conflict)',
|
|
|
- conflict: true,
|
|
|
- }))
|
|
|
+ const updateTask = vi.fn(() => Promise.resolve(taskConflict('stale')))
|
|
|
render(<TeamAction {...props(actions({ load, updateTask }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
@@ -300,24 +322,24 @@ describe('TeamAction', () => {
|
|
|
it('keeps reload failures visible after task and dependency conflicts', async () => {
|
|
|
const taskLoad = vi.fn()
|
|
|
.mockResolvedValueOnce({ ok: true, value: view })
|
|
|
- .mockResolvedValueOnce({ ok: false, error: 'task reload failed', conflict: false })
|
|
|
+ .mockResolvedValueOnce(remoteFailure('task reload failed'))
|
|
|
const first = render(<TeamAction {...props(actions({
|
|
|
load: taskLoad,
|
|
|
- updateTask: () => Promise.resolve({ ok: false, error: 'stale task', conflict: true }),
|
|
|
+ updateTask: () => Promise.resolve(taskConflict('stale task')),
|
|
|
}))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
fireEvent.click(screen.getByRole('button', { name: /完成/u }))
|
|
|
- expect(await screen.findByText('task reload failed')).toBeTruthy()
|
|
|
+ expect(await screen.findByText('task reload failed (internal)')).toBeTruthy()
|
|
|
expect(screen.queryByText(zh.conflict)).toBeNull()
|
|
|
first.unmount()
|
|
|
|
|
|
const dependencyLoad = vi.fn()
|
|
|
.mockResolvedValueOnce({ ok: true, value: view })
|
|
|
- .mockResolvedValueOnce({ ok: false, error: 'dependency reload failed', conflict: false })
|
|
|
+ .mockResolvedValueOnce(remoteFailure('dependency reload failed'))
|
|
|
const dependencyUpdate = vi.fn()
|
|
|
- .mockResolvedValueOnce({ ok: true, value: { ...task, revision: 2, subject: 'Edited' } })
|
|
|
- .mockResolvedValueOnce({ ok: false, error: 'stale dependency', conflict: true })
|
|
|
+ .mockResolvedValueOnce(taskSuccess({ ...task, revision: 2, subject: 'Edited' }))
|
|
|
+ .mockResolvedValueOnce(taskConflict('stale dependency'))
|
|
|
render(<TeamAction {...props(actions({ load: dependencyLoad, updateTask: dependencyUpdate }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
@@ -325,7 +347,7 @@ describe('TeamAction', () => {
|
|
|
fireEvent.change(screen.getByPlaceholderText('任务标题'), { target: { value: 'Edited' } })
|
|
|
fireEvent.change(screen.getByPlaceholderText(zh.blockers), { target: { value: 'task-2' } })
|
|
|
fireEvent.click(screen.getByRole('button', { name: '保存' }))
|
|
|
- expect(await screen.findByText('dependency reload failed')).toBeTruthy()
|
|
|
+ expect(await screen.findByText('dependency reload failed (internal)')).toBeTruthy()
|
|
|
expect(screen.queryByText(zh.conflict)).toBeNull()
|
|
|
})
|
|
|
|
|
|
@@ -337,14 +359,14 @@ describe('TeamAction', () => {
|
|
|
view.members[0]!,
|
|
|
{ ...view.members[1]!, status: 'running' },
|
|
|
{
|
|
|
- id: 'failed-id',
|
|
|
+ id: 'failed-id' as SessionId,
|
|
|
name: 'failed-worker',
|
|
|
role: 'teammate',
|
|
|
status: 'failed',
|
|
|
diagnostics: ['provider failed'],
|
|
|
},
|
|
|
{
|
|
|
- id: 'provisioning-id',
|
|
|
+ id: 'provisioning-id' as SessionId,
|
|
|
name: 'provisioning-worker',
|
|
|
role: 'teammate',
|
|
|
status: 'provisioning',
|
|
|
@@ -352,9 +374,9 @@ describe('TeamAction', () => {
|
|
|
},
|
|
|
],
|
|
|
tasks: [
|
|
|
- { ...unownedTask, id: 'ready-task', status: 'pending', ready: true },
|
|
|
- { ...unownedTask, id: 'blocked-task', status: 'pending', ready: false },
|
|
|
- { ...task, id: 'completed-task', status: 'completed' },
|
|
|
+ { ...unownedTask, id: 'ready-task' as TeamTaskId, status: 'pending', ready: true },
|
|
|
+ { ...unownedTask, id: 'blocked-task' as TeamTaskId, status: 'pending', ready: false },
|
|
|
+ { ...task, id: 'completed-task' as TeamTaskId, status: 'completed' },
|
|
|
],
|
|
|
}
|
|
|
const load = vi.fn(() => Promise.resolve({ ok: true as const, value: richView }))
|
|
|
@@ -381,16 +403,14 @@ describe('TeamAction', () => {
|
|
|
|
|
|
it('shows load and create failures and ignores a create result after a session switch', async () => {
|
|
|
const failedLoad = actions({
|
|
|
- load: () => Promise.resolve({ ok: false, error: 'load failed', conflict: false }),
|
|
|
+ load: () => Promise.resolve(remoteFailure('load failed')),
|
|
|
})
|
|
|
const first = render(<TeamAction {...props(failedLoad)} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
- expect(await screen.findByText('load failed')).toBeTruthy()
|
|
|
+ expect(await screen.findByText('load failed (internal)')).toBeTruthy()
|
|
|
first.unmount()
|
|
|
|
|
|
- const createTask = vi.fn(() => Promise.resolve({
|
|
|
- ok: false as const, error: 'create failed', conflict: false,
|
|
|
- }))
|
|
|
+ const createTask = vi.fn(() => Promise.resolve(remoteFailure('create failed')))
|
|
|
const second = render(<TeamAction {...props(actions({ createTask }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
@@ -398,7 +418,7 @@ describe('TeamAction', () => {
|
|
|
fireEvent.change(screen.getByPlaceholderText('任务标题'), { target: { value: 'Task' } })
|
|
|
fireEvent.change(screen.getByPlaceholderText('任务描述'), { target: { value: 'Description' } })
|
|
|
fireEvent.click(screen.getByRole('button', { name: '保存' }))
|
|
|
- expect(await screen.findByText('create failed')).toBeTruthy()
|
|
|
+ expect(await screen.findByText('create failed (internal)')).toBeTruthy()
|
|
|
second.unmount()
|
|
|
|
|
|
const pending = Promise.withResolvers<TeamActionResult<TeamTask>>()
|
|
|
@@ -410,30 +430,30 @@ describe('TeamAction', () => {
|
|
|
fireEvent.change(screen.getByPlaceholderText('任务描述'), { target: { value: 'Late description' } })
|
|
|
fireEvent.click(screen.getByRole('button', { name: '保存' }))
|
|
|
third.rerender(<TeamAction {...props(actions(), 'next-session' as SessionId)} />)
|
|
|
- pending.resolve({ ok: true, value: { ...task, id: 'late-task' } })
|
|
|
+ pending.resolve({ ok: true, value: { ...task, id: 'late-task' as TeamTaskId } })
|
|
|
await Promise.resolve()
|
|
|
expect(screen.queryByText('Late task')).toBeNull()
|
|
|
})
|
|
|
|
|
|
it('contains stale-session and ordinary task failures without retrying', async () => {
|
|
|
- const pending = Promise.withResolvers<TeamActionResult<TeamTask>>()
|
|
|
+ const pending = Promise.withResolvers<TeamTaskActionResult>()
|
|
|
const rendered = render(<TeamAction {...props(actions({ updateTask: () => pending.promise }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
fireEvent.click(screen.getByRole('button', { name: /完成/u }))
|
|
|
rendered.rerender(<TeamAction {...props(actions(), 'next-session' as SessionId)} />)
|
|
|
- pending.resolve({ ok: true, value: { ...task, revision: 2, status: 'completed' } })
|
|
|
+ pending.resolve(taskSuccess({ ...task, revision: 2, status: 'completed' }))
|
|
|
await Promise.resolve()
|
|
|
expect(screen.queryByText('Implement runtime')).toBeNull()
|
|
|
rendered.unmount()
|
|
|
|
|
|
render(<TeamAction {...props(actions({
|
|
|
- updateTask: () => Promise.resolve({ ok: false, error: 'update failed', conflict: false }),
|
|
|
+ updateTask: () => Promise.resolve(taskRejected('update failed')),
|
|
|
}))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
fireEvent.click(screen.getByRole('button', { name: /完成/u }))
|
|
|
- expect(await screen.findByText('update failed')).toBeTruthy()
|
|
|
+ expect(await screen.findByText('update failed (team-rejected)')).toBeTruthy()
|
|
|
})
|
|
|
|
|
|
it('does not publish a task conflict after its reload switches sessions', async () => {
|
|
|
@@ -443,7 +463,7 @@ describe('TeamAction', () => {
|
|
|
.mockImplementationOnce(() => reload.promise)
|
|
|
const rendered = render(<TeamAction {...props(actions({
|
|
|
load,
|
|
|
- updateTask: () => Promise.resolve({ ok: false, error: 'stale task', conflict: true }),
|
|
|
+ updateTask: () => Promise.resolve(taskConflict('stale task')),
|
|
|
}))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
@@ -458,11 +478,12 @@ describe('TeamAction', () => {
|
|
|
})
|
|
|
|
|
|
it('contains edit and dependency failures and supports form cancellation and unassignment', async () => {
|
|
|
+ const { ownerName: _ownerName, ...unownedTask } = task
|
|
|
const updateTask = vi.fn()
|
|
|
- .mockResolvedValueOnce({ ok: false, error: 'edit failed', conflict: false })
|
|
|
- .mockResolvedValueOnce({ ok: true, value: { ...task, revision: 2, subject: 'Saved edit' } })
|
|
|
- .mockResolvedValueOnce({ ok: false, error: 'dependency failed', conflict: false })
|
|
|
- .mockResolvedValueOnce({ ok: true, value: { ...task, revision: 2, ownerName: undefined } })
|
|
|
+ .mockResolvedValueOnce(remoteFailure('edit failed'))
|
|
|
+ .mockResolvedValueOnce(taskSuccess({ ...task, revision: 2, subject: 'Saved edit' }))
|
|
|
+ .mockResolvedValueOnce(taskRejected('dependency failed'))
|
|
|
+ .mockResolvedValueOnce(taskSuccess({ ...unownedTask, revision: 2 }))
|
|
|
render(<TeamAction {...props(actions({ updateTask }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
@@ -476,12 +497,12 @@ describe('TeamAction', () => {
|
|
|
expect(screen.queryByRole('button', { name: '保存' })).toBeNull()
|
|
|
fireEvent.click(screen.getByRole('button', { name: /编辑/u }))
|
|
|
fireEvent.click(screen.getByRole('button', { name: '保存' }))
|
|
|
- expect(await screen.findByText('edit failed')).toBeTruthy()
|
|
|
+ expect(await screen.findByText('edit failed (internal)')).toBeTruthy()
|
|
|
|
|
|
fireEvent.change(screen.getByPlaceholderText('任务标题'), { target: { value: 'Saved edit' } })
|
|
|
fireEvent.change(screen.getByPlaceholderText(zh.blockers), { target: { value: 'task-2' } })
|
|
|
fireEvent.click(screen.getByRole('button', { name: '保存' }))
|
|
|
- expect(await screen.findByText('dependency failed')).toBeTruthy()
|
|
|
+ expect(await screen.findByText('dependency failed (team-rejected)')).toBeTruthy()
|
|
|
expect(updateTask.mock.calls[2]?.[1]).toMatchObject({
|
|
|
action: 'set_dependencies',
|
|
|
expectedRevision: 2,
|
|
|
@@ -498,12 +519,26 @@ describe('TeamAction', () => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+ it('shows a Remote carrier failure from the dependency mutation', async () => {
|
|
|
+ const updateTask = vi.fn()
|
|
|
+ .mockResolvedValueOnce(taskSuccess({ ...task, revision: 2, subject: 'Edited' }))
|
|
|
+ .mockResolvedValueOnce(remoteFailure('dependency transport failed'))
|
|
|
+ render(<TeamAction {...props(actions({ updateTask }))} />)
|
|
|
+ fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
+ await screen.findByText('Implement runtime')
|
|
|
+ fireEvent.click(screen.getByRole('button', { name: /编辑/u }))
|
|
|
+ fireEvent.change(screen.getByPlaceholderText('任务标题'), { target: { value: 'Edited' } })
|
|
|
+ fireEvent.change(screen.getByPlaceholderText(zh.blockers), { target: { value: 'task-2' } })
|
|
|
+ fireEvent.click(screen.getByRole('button', { name: '保存' }))
|
|
|
+
|
|
|
+ expect(await screen.findByText('dependency transport failed (internal)')).toBeTruthy()
|
|
|
+ })
|
|
|
+
|
|
|
it('skips the dependency mutation when an edit keeps the same blockers', async () => {
|
|
|
- const blockedTask: TeamTask = { ...task, blockedBy: ['task-0'] }
|
|
|
- const updateTask = vi.fn().mockResolvedValue({
|
|
|
- ok: true,
|
|
|
- value: { ...blockedTask, revision: 2, subject: 'Same dependencies' },
|
|
|
- })
|
|
|
+ const blockedTask: TeamTask = { ...task, blockedBy: ['task-0' as TeamTaskId] }
|
|
|
+ const updateTask = vi.fn().mockResolvedValue(
|
|
|
+ taskSuccess({ ...blockedTask, revision: 2, subject: 'Same dependencies' }),
|
|
|
+ )
|
|
|
render(<TeamAction {...props(actions({
|
|
|
load: () => Promise.resolve({ ok: true, value: { ...view, tasks: [blockedTask] } }),
|
|
|
updateTask,
|
|
|
@@ -524,8 +559,8 @@ describe('TeamAction', () => {
|
|
|
.mockResolvedValueOnce({ ok: true, value: view })
|
|
|
.mockResolvedValueOnce({ ok: true, value: { ...view, tasks: [{ ...task, revision: 3 }] } })
|
|
|
const conflictUpdate = vi.fn()
|
|
|
- .mockResolvedValueOnce({ ok: true, value: { ...task, revision: 2, subject: 'Conflict edit' } })
|
|
|
- .mockResolvedValueOnce({ ok: false, error: 'stale dependency', conflict: true })
|
|
|
+ .mockResolvedValueOnce(taskSuccess({ ...task, revision: 2, subject: 'Conflict edit' }))
|
|
|
+ .mockResolvedValueOnce(taskConflict('stale dependency'))
|
|
|
const first = render(<TeamAction {...props(actions({ load, updateTask: conflictUpdate }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
@@ -542,8 +577,8 @@ describe('TeamAction', () => {
|
|
|
.mockResolvedValueOnce({ ok: true, value: view })
|
|
|
.mockImplementationOnce(() => dependencyReload.promise)
|
|
|
const staleUpdate = vi.fn()
|
|
|
- .mockResolvedValueOnce({ ok: true, value: { ...task, revision: 2, subject: 'Late edit' } })
|
|
|
- .mockResolvedValueOnce({ ok: false, error: 'stale dependency', conflict: true })
|
|
|
+ .mockResolvedValueOnce(taskSuccess({ ...task, revision: 2, subject: 'Late edit' }))
|
|
|
+ .mockResolvedValueOnce(taskConflict('stale dependency'))
|
|
|
const second = render(<TeamAction {...props(actions({ load: dependencyLoad, updateTask: staleUpdate }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
await screen.findByText('Implement runtime')
|
|
|
@@ -559,9 +594,9 @@ describe('TeamAction', () => {
|
|
|
expect(screen.queryByText(zh.conflict)).toBeNull()
|
|
|
second.unmount()
|
|
|
|
|
|
- const dependency = Promise.withResolvers<TeamActionResult<TeamTask>>()
|
|
|
+ const dependency = Promise.withResolvers<TeamTaskActionResult>()
|
|
|
const lateUpdate = vi.fn()
|
|
|
- .mockResolvedValueOnce({ ok: true, value: { ...task, revision: 2, subject: 'Late edit' } })
|
|
|
+ .mockResolvedValueOnce(taskSuccess({ ...task, revision: 2, subject: 'Late edit' }))
|
|
|
.mockImplementationOnce(() => dependency.promise)
|
|
|
const third = render(<TeamAction {...props(actions({ updateTask: lateUpdate }))} />)
|
|
|
fireEvent.click(screen.getByRole('button', { name: /Agent Team/u }))
|
|
|
@@ -574,7 +609,7 @@ describe('TeamAction', () => {
|
|
|
expect(screen.getByRole<HTMLButtonElement>('button', { name: '保存' }).disabled).toBe(true)
|
|
|
expect(screen.getByRole<HTMLButtonElement>('button', { name: '取消' }).disabled).toBe(true)
|
|
|
third.rerender(<TeamAction {...props(actions(), 'next-session' as SessionId)} />)
|
|
|
- dependency.resolve({ ok: true, value: { ...task, revision: 3, subject: 'Late dependency' } })
|
|
|
+ dependency.resolve(taskSuccess({ ...task, revision: 3, subject: 'Late dependency' }))
|
|
|
await Promise.resolve()
|
|
|
expect(screen.queryByText('Late dependency')).toBeNull()
|
|
|
})
|