Files
makelore/tests/unit/setup-page.test.tsx
T
brother7 5a275b93a7 feat: remove legacy OpenCode runtime
Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
2026-08-24 12:17:43 +08:00

125 lines
3.9 KiB
TypeScript

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 { 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,
});
}
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({ runtime: 'pi' });
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.codingRuntime')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'runtime.viewLogs' })).not.toBeInTheDocument();
});
it('shows only a retry action when runtime checks fail', async () => {
hostApiFetchMock.mockResolvedValue({ runtime: 'unavailable' });
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({ runtime: 'pi' });
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();
});
});