Files
makelore/tests/unit/setup-page.test.tsx
2026-07-29 17:22:35 +08:00

162 lines
4.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import Setup from '@/pages/Setup';
import { useAuthStore } from '@/stores/auth';
import { useOpencodeStore } from '@/stores/opencode';
import { useSettingsStore } from '@/stores/settings';
const { hostApiFetchMock, translateMock } = vi.hoisted(() => ({
hostApiFetchMock: vi.fn(),
translateMock: vi.fn((key: string, options?: { returnObjects?: boolean }) => (
options?.returnObjects ? [`${key}.0`, `${key}.1`, `${key}.2`] : key
)),
}));
vi.mock('@/lib/host-api', () => ({
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
}));
vi.mock('@/lib/api-client', () => ({
invokeIpc: vi.fn(),
}));
vi.mock('react-i18next', async (importOriginal) => {
const actual = await importOriginal<typeof import('react-i18next')>();
return {
...actual,
useTranslation: () => ({
t: translateMock,
}),
};
});
vi.mock('sonner', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
},
}));
function renderSetup() {
return render(
<MemoryRouter initialEntries={['/setup']}>
<Setup />
</MemoryRouter>,
);
}
function resetStores() {
useAuthStore.setState({ user: null });
useSettingsStore.setState({
setupComplete: false,
theme: 'system',
language: 'en',
launchAtStartup: false,
telemetryEnabled: false,
proxyEnabled: false,
proxyServer: '',
proxyHttpServer: '',
proxyHttpsServer: '',
proxyAllServer: '',
proxyBypassRules: '',
sidebarCollapsed: false,
devModeUnlocked: false,
});
useOpencodeStore.setState({
status: { state: 'stopped', port: 4096 },
health: null,
healthCheckedAt: null,
runtimeConfigSummary: null,
projects: [],
activeProject: null,
sessions: [],
sessionsByProjectId: {},
selectedSessionId: null,
sessionStatuses: {},
sessionMessages: [],
sessionMessagesBySessionId: {},
streamingMessage: null,
streamingMessagesBySessionId: {},
streamingTools: [],
streamingToolsBySessionId: {},
sendingSessionId: null,
sendingSessionIds: {},
queuedSessionPrompts: {},
sessionDiffs: [],
sessionDiffLoading: false,
sessionDiffError: null,
fileStatuses: [],
fileSearchResults: [],
contentSearchResults: [],
selectedContextFile: null,
fileContextLoading: false,
fileContextError: null,
runtimeAutoStartAttempted: false,
loading: false,
error: null,
});
}
describe('Setup first-run progress flow', () => {
beforeEach(() => {
vi.clearAllMocks();
resetStores();
});
it('greets the authenticated user by the normalized account name', () => {
useAuthStore.setState({
user: { username: 'zhangsan@example.com', userId: 'user-1', tenantId: null, deptId: null, authorities: [] },
});
renderSetup();
expect(screen.getByRole('heading', { name: 'zhangsan欢迎回来' })).toBeInTheDocument();
});
it('renders runtime checks as a quiet progress flow without dependency details', async () => {
hostApiFetchMock.mockResolvedValue({ state: 'stopped', port: 4096 });
renderSetup();
fireEvent.click(screen.getByTestId('setup-next-button'));
expect(await screen.findByTestId('setup-runtime-progress')).toBeInTheDocument();
expect(screen.getByRole('progressbar')).toBeInTheDocument();
expect(screen.queryByText('runtime.nodejs')).not.toBeInTheDocument();
expect(screen.queryByText('runtime.opencode')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'runtime.viewLogs' })).not.toBeInTheDocument();
});
it('shows only a retry action when runtime checks fail', async () => {
hostApiFetchMock.mockResolvedValue({
state: 'error',
error: 'missing bundled runtime package at /very/noisy/path',
});
renderSetup();
fireEvent.click(screen.getByTestId('setup-next-button'));
expect(await screen.findByTestId('setup-runtime-retry')).toBeInTheDocument();
expect(screen.queryByText(/missing bundled runtime package/)).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'runtime.viewLogs' })).not.toBeInTheDocument();
});
it('keeps dependency installation focused on overall progress', async () => {
hostApiFetchMock.mockResolvedValue({ state: 'stopped', port: 4096 });
renderSetup();
fireEvent.click(screen.getByTestId('setup-next-button'));
await waitFor(() => expect(screen.getByTestId('setup-next-button')).toBeEnabled());
fireEvent.click(screen.getByTestId('setup-next-button'));
expect(await screen.findByTestId('setup-installing-progress')).toBeInTheDocument();
expect(screen.queryByText('defaultSkills.python-env.name')).not.toBeInTheDocument();
expect(screen.queryByText('defaultSkills.file-tools.name')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'installing.skip' })).not.toBeInTheDocument();
});
});