fix: show empty work preview without invoking the Agent
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { CodingWorkPreviewService, workPageResponds } from '../../electron/coding-runtime/work-preview';
|
||||
import { inspectWorkPresence, type WorkPresence } from '../../electron/coding-runtime/work-presence';
|
||||
import { OPEN_WORK_PROMPT } from '../../shared/coding-work-preview';
|
||||
import { firstMessageTitle } from '../../electron/coding-runtime/conversation-title';
|
||||
import type { AgentBrowserSnapshot } from '../../shared/agent-browser';
|
||||
@@ -21,13 +25,84 @@ function setup() {
|
||||
const runtime = { getDiagnostics: vi.fn(() => ({ revision: { provider: 0, resources: 0 }, workers: [] })) };
|
||||
const ensureActive = vi.fn(async () => {});
|
||||
const responds = vi.fn(async () => false);
|
||||
const service = new CodingWorkPreviewService({ browser, conversations, runtime, ensureActive, responds });
|
||||
return { service, browser, conversations, runtime, ensureActive, responds, snapshot };
|
||||
const inspectWork = vi.fn(async (_projectPath: string): Promise<WorkPresence> => 'possible');
|
||||
const service = new CodingWorkPreviewService({ browser, conversations, runtime, ensureActive, responds, inspectWork });
|
||||
return { service, browser, conversations, runtime, ensureActive, responds, inspectWork, snapshot };
|
||||
}
|
||||
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
const directories: string[] = [];
|
||||
afterEach(async () => {
|
||||
vi.unstubAllGlobals();
|
||||
await Promise.all(directories.splice(0).map(directory => rm(directory, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
describe('opening a work preview', () => {
|
||||
it('keeps repeated empty-project clicks out of conversations and discovers work made later', async () => {
|
||||
const s = setup();
|
||||
const path = await mkdtemp(join(tmpdir(), 'makelore-empty-preview-'));
|
||||
directories.push(path);
|
||||
const emptyProject = { ...project, path };
|
||||
await mkdir(join(path, '.makelore'));
|
||||
await mkdir(join(path, 'knowledge'));
|
||||
await writeFile(join(path, '.makelore/project.json'), '{}');
|
||||
await writeFile(join(path, '头像.png'), 'image');
|
||||
s.inspectWork.mockImplementation(inspectWorkPresence);
|
||||
s.conversations.listConversations.mockResolvedValue([]);
|
||||
for (const requestId of ['req', 'req', 'new-click']) {
|
||||
expect(await s.service.ensure(emptyProject, requestId)).toEqual({ status: 'empty' });
|
||||
}
|
||||
expect(s.conversations.createConversation).not.toHaveBeenCalled();
|
||||
expect(s.conversations.getConversation).not.toHaveBeenCalled();
|
||||
expect(s.conversations.getSnapshot).not.toHaveBeenCalled();
|
||||
expect(s.conversations.acceptPrompt).not.toHaveBeenCalled();
|
||||
expect(s.browser.open).not.toHaveBeenCalled();
|
||||
|
||||
await mkdir(join(path, 'game'));
|
||||
await writeFile(join(path, 'game/play.html'), '<h1>Game</h1>');
|
||||
expect(await s.service.ensure(emptyProject, 'new-click')).toMatchObject({ status: 'starting' });
|
||||
expect(s.conversations.createConversation).toHaveBeenCalledTimes(1);
|
||||
expect(s.conversations.acceptPrompt).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('preserves startup when a local check cannot establish absence', async () => {
|
||||
const s = setup();
|
||||
s.inspectWork.mockResolvedValue('unknown');
|
||||
expect(await s.service.ensure(project, 'req', 'c1')).toMatchObject({ status: 'starting' });
|
||||
expect(s.conversations.acceptPrompt).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('waits for ongoing creation before showing an empty state', async () => {
|
||||
const s = setup();
|
||||
s.inspectWork.mockResolvedValue('empty');
|
||||
s.runtime.getDiagnostics.mockReturnValueOnce({ revision: { provider: 0, resources: 0 }, workers: [{ conversationId: 'c1', stage: 'running' }] } as never);
|
||||
expect(await s.service.ensure(project, 'req', 'c1')).toEqual({ status: 'waiting' });
|
||||
expect(s.inspectWork).not.toHaveBeenCalled();
|
||||
expect(await s.service.ensure(project, 'req', 'c1')).toEqual({ status: 'empty' });
|
||||
expect(s.conversations.acceptPrompt).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('revalidates the active project after an asynchronous presence check', async () => {
|
||||
const s = setup();
|
||||
s.inspectWork.mockImplementation(async () => {
|
||||
s.ensureActive.mockRejectedValue(new Error('project changed'));
|
||||
return 'empty';
|
||||
});
|
||||
await expect(s.service.ensure(project, 'req', 'c1')).rejects.toThrow('project changed');
|
||||
expect(s.conversations.createConversation).not.toHaveBeenCalled();
|
||||
expect(s.conversations.acceptPrompt).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('waits if creation starts during the local presence check', async () => {
|
||||
const s = setup();
|
||||
s.inspectWork.mockImplementation(async () => {
|
||||
s.runtime.getDiagnostics.mockReturnValue({ revision: { provider: 0, resources: 0 }, workers: [{ conversationId: 'c1', stage: 'running' }] } as never);
|
||||
return 'empty';
|
||||
});
|
||||
expect(await s.service.ensure(project, 'req', 'c1')).toEqual({ status: 'waiting' });
|
||||
expect(s.conversations.createConversation).not.toHaveBeenCalled();
|
||||
expect(s.conversations.acceptPrompt).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('lets a successful manual open supersede a pending startup without a late Agent request', async () => {
|
||||
const s = setup();
|
||||
let finish!: (value: ConversationSnapshot) => void;
|
||||
@@ -82,6 +157,7 @@ describe('opening a work preview', () => {
|
||||
expect(await s.service.ensure(project, 'req', 'c1')).toMatchObject({ status: 'ready', browser: s.snapshot });
|
||||
expect(s.conversations.acceptPrompt).not.toHaveBeenCalled();
|
||||
expect(s.browser.open).not.toHaveBeenCalled();
|
||||
expect(s.inspectWork).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('restores a crashed preview while keeping a responding service', async () => {
|
||||
|
||||
Reference in New Issue
Block a user