Makelore 2.0 initial clean snapshot
This commit is contained in:
163
tests/unit/projects-page.test.tsx
Normal file
163
tests/unit/projects-page.test.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { Projects } from '@/pages/Projects';
|
||||
import { useOpencodeStore } from '@/stores/opencode';
|
||||
import { createProjectConfig } from '../../shared/project-config';
|
||||
|
||||
const hostApiFetchMock = vi.fn();
|
||||
const invokeIpcMock = vi.fn();
|
||||
|
||||
vi.mock('@/lib/host-api', () => ({
|
||||
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
|
||||
ensureHostApiToken: vi.fn().mockResolvedValue('test-token'),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/api-client', () => ({
|
||||
invokeIpc: (...args: unknown[]) => invokeIpcMock(...args),
|
||||
}));
|
||||
|
||||
describe('Projects page', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
useOpencodeStore.setState({
|
||||
status: { state: 'stopped', port: 4096 },
|
||||
health: null,
|
||||
projects: [],
|
||||
activeProject: null,
|
||||
sessions: [],
|
||||
sessionsByProjectId: {},
|
||||
selectedSessionId: null,
|
||||
sessionStatuses: {},
|
||||
sessionMessages: [],
|
||||
sessionMessagesBySessionId: {},
|
||||
streamingMessage: null,
|
||||
streamingMessagesBySessionId: {},
|
||||
streamingTools: [],
|
||||
streamingToolsBySessionId: {},
|
||||
sendingSessionId: null,
|
||||
sendingSessionIds: {},
|
||||
queuedSessionPrompts: {},
|
||||
runtimeAutoStartAttempted: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders projects loaded from the opencode project store', async () => {
|
||||
hostApiFetchMock.mockResolvedValueOnce({
|
||||
projects: [{
|
||||
id: 'prj_1',
|
||||
path: 'D:/repo/packages/ui',
|
||||
name: 'ui',
|
||||
createdAt: '2026-05-12T00:00:00.000Z',
|
||||
updatedAt: '2026-05-12T00:00:00.000Z',
|
||||
lastOpenedAt: '2026-05-12T00:00:00.000Z',
|
||||
}],
|
||||
activeProject: null,
|
||||
});
|
||||
|
||||
render(<Projects />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('ui')).toBeInTheDocument());
|
||||
expect(screen.getByText('D:/repo/packages/ui')).toBeInTheDocument();
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects');
|
||||
});
|
||||
|
||||
it('opens a folder through the native picker', async () => {
|
||||
const project = {
|
||||
id: 'prj_2',
|
||||
path: 'D:/repo/packages/api',
|
||||
name: 'api',
|
||||
createdAt: '2026-05-12T00:00:00.000Z',
|
||||
updatedAt: '2026-05-12T00:00:00.000Z',
|
||||
lastOpenedAt: '2026-05-12T00:00:00.000Z',
|
||||
};
|
||||
hostApiFetchMock
|
||||
.mockResolvedValueOnce({ projects: [], activeProject: null })
|
||||
.mockResolvedValueOnce({
|
||||
success: true,
|
||||
project,
|
||||
projects: [project],
|
||||
activeProject: project,
|
||||
});
|
||||
invokeIpcMock.mockResolvedValueOnce({
|
||||
canceled: false,
|
||||
filePaths: ['D:/repo/packages/api'],
|
||||
});
|
||||
|
||||
render(<Projects />);
|
||||
await screen.findByText('No projects yet');
|
||||
fireEvent.click(screen.getAllByRole('button', { name: /open folder/i })[0]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invokeIpcMock).toHaveBeenCalledWith('dialog:open', {
|
||||
properties: ['openDirectory'],
|
||||
title: 'Open Project Folder',
|
||||
});
|
||||
});
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/open', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ path: 'D:/repo/packages/api' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not activate a project whose configuration is missing', async () => {
|
||||
const project = {
|
||||
id: 'prj_missing_template',
|
||||
path: 'D:/repo/packages/api',
|
||||
name: 'api',
|
||||
createdAt: '2026-05-12T00:00:00.000Z',
|
||||
updatedAt: '2026-05-12T00:00:00.000Z',
|
||||
lastOpenedAt: '2026-05-12T00:00:00.000Z',
|
||||
};
|
||||
hostApiFetchMock
|
||||
.mockResolvedValueOnce({ projects: [project], activeProject: null })
|
||||
.mockResolvedValueOnce({ status: 'missing' });
|
||||
|
||||
render(<Projects />);
|
||||
|
||||
const useButton = await screen.findByRole('button', { name: 'Use' });
|
||||
fireEvent.click(useButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith(
|
||||
`/api/opencode/projects/config?projectId=${encodeURIComponent(project.id)}`,
|
||||
);
|
||||
});
|
||||
expect(useOpencodeStore.getState().activeProject).toBeNull();
|
||||
expect(await screen.findByText(/configuration is missing/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('activates a project only after its configuration validates', async () => {
|
||||
const project = {
|
||||
id: 'prj_valid_template',
|
||||
path: 'D:/repo/packages/ui',
|
||||
name: 'ui',
|
||||
createdAt: '2026-05-12T00:00:00.000Z',
|
||||
updatedAt: '2026-05-12T00:00:00.000Z',
|
||||
lastOpenedAt: '2026-05-12T00:00:00.000Z',
|
||||
};
|
||||
const config = { ...createProjectConfig('standard-development'), initialized: true };
|
||||
hostApiFetchMock
|
||||
.mockResolvedValueOnce({ projects: [project], activeProject: null })
|
||||
.mockResolvedValueOnce({ status: 'valid', config, knowledgeFiles: [] })
|
||||
.mockResolvedValueOnce({
|
||||
success: true,
|
||||
project,
|
||||
projects: [project],
|
||||
activeProject: project,
|
||||
});
|
||||
|
||||
render(<Projects />);
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Use' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/active', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ projectId: project.id }),
|
||||
});
|
||||
});
|
||||
expect(useOpencodeStore.getState().activeProject).toEqual(project);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user