198 lines
5.9 KiB
TypeScript
198 lines
5.9 KiB
TypeScript
import React, { act } from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { TmuxInstallerBannerView } from '../TmuxInstallerBannerView';
|
|
|
|
import type { TmuxInstallerBannerViewModel } from '../../adapters/TmuxInstallerBannerAdapter';
|
|
|
|
const { mockUseTmuxInstallerBanner } = vi.hoisted(() => ({
|
|
mockUseTmuxInstallerBanner: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('../../hooks/useTmuxInstallerBanner', () => ({
|
|
useTmuxInstallerBanner: mockUseTmuxInstallerBanner,
|
|
}));
|
|
|
|
const baseViewModel: TmuxInstallerBannerViewModel = {
|
|
visible: true,
|
|
loading: false,
|
|
title: 'tmux is not installed',
|
|
body: 'WSL is available, but no Linux distribution is installed yet.',
|
|
benefitsBody:
|
|
'Optional, but recommended. The app works without tmux. With tmux in WSL, teammates are more reliable. Without tmux, creating multi-agent teams that mix providers may be blocked.',
|
|
error: null,
|
|
platformLabel: 'Windows',
|
|
locationLabel: null,
|
|
runtimeReadyLabel: null,
|
|
versionLabel: null,
|
|
phase: 'idle',
|
|
progressPercent: null,
|
|
logs: [],
|
|
manualHints: [
|
|
{
|
|
title: 'Install WSL',
|
|
description: 'Install Windows Subsystem for Linux.',
|
|
command: 'wsl --install --no-distribution',
|
|
},
|
|
{
|
|
title: 'Install Ubuntu',
|
|
description: 'Recommended WSL distro.',
|
|
command: 'wsl --install -d Ubuntu --no-launch',
|
|
},
|
|
],
|
|
manualHintsCollapsible: true,
|
|
primaryGuideUrl: 'https://example.com/guide',
|
|
installSupported: true,
|
|
installDisabled: false,
|
|
installLabel: 'Install Ubuntu in WSL',
|
|
installButtonPrimary: true,
|
|
showRefreshButton: true,
|
|
canCancel: false,
|
|
acceptsInput: false,
|
|
inputPrompt: null,
|
|
inputSecret: false,
|
|
detailsOpen: false,
|
|
};
|
|
|
|
function renderBanner(viewModel: TmuxInstallerBannerViewModel): {
|
|
host: HTMLDivElement;
|
|
root: ReturnType<typeof createRoot>;
|
|
} {
|
|
const host = document.createElement('div');
|
|
document.body.appendChild(host);
|
|
const root = createRoot(host);
|
|
mockUseTmuxInstallerBanner.mockReturnValue({
|
|
viewModel,
|
|
install: vi.fn(),
|
|
cancel: vi.fn(),
|
|
submitInput: vi.fn(),
|
|
refresh: vi.fn(),
|
|
toggleDetails: vi.fn(),
|
|
openExternal: vi.fn(),
|
|
});
|
|
|
|
act(() => {
|
|
root.render(React.createElement(TmuxInstallerBannerView));
|
|
});
|
|
|
|
return { host, root };
|
|
}
|
|
|
|
describe('TmuxInstallerBannerView', () => {
|
|
beforeEach(() => {
|
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
|
});
|
|
|
|
afterEach(() => {
|
|
mockUseTmuxInstallerBanner.mockReset();
|
|
document.body.innerHTML = '';
|
|
});
|
|
|
|
it('keeps Windows setup steps collapsed by default and expands them on demand', async () => {
|
|
const { host, root } = renderBanner(baseViewModel);
|
|
|
|
expect(host.textContent).toContain('tmux is not installed');
|
|
expect(host.textContent).toContain('Optional, but recommended');
|
|
expect(host.textContent).toContain('multi-agent teams that mix providers');
|
|
expect(host.textContent).not.toContain(
|
|
'WSL is available, but no Linux distribution is installed yet.'
|
|
);
|
|
expect(host.textContent).not.toContain('Show setup steps (2)');
|
|
expect(host.textContent).not.toContain('wsl --install --no-distribution');
|
|
|
|
const summaryButton = [...host.querySelectorAll('button')].find((button) =>
|
|
button.textContent?.includes('tmux is not installed')
|
|
);
|
|
expect(summaryButton).toBeDefined();
|
|
|
|
await act(async () => {
|
|
summaryButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
await Promise.resolve();
|
|
});
|
|
|
|
expect(host.textContent).toContain(
|
|
'WSL is available, but no Linux distribution is installed yet.'
|
|
);
|
|
expect(host.textContent).toContain('Show setup steps (2)');
|
|
expect(host.textContent).not.toContain('Hide setup steps');
|
|
|
|
const setupToggle = [...host.querySelectorAll('button')].find((button) =>
|
|
button.textContent?.includes('Show setup steps')
|
|
);
|
|
expect(setupToggle).toBeDefined();
|
|
|
|
await act(async () => {
|
|
setupToggle?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
await Promise.resolve();
|
|
});
|
|
|
|
expect(host.textContent).toContain('Hide setup steps');
|
|
expect(host.textContent).toContain('wsl --install --no-distribution');
|
|
expect(host.textContent).toContain('wsl --install -d Ubuntu --no-launch');
|
|
|
|
act(() => {
|
|
root.unmount();
|
|
});
|
|
});
|
|
|
|
it('shows setup hints immediately on non-Windows platforms', () => {
|
|
const { host, root } = renderBanner({
|
|
...baseViewModel,
|
|
platformLabel: 'macOS',
|
|
manualHintsCollapsible: false,
|
|
manualHints: [
|
|
{ title: 'Homebrew', description: 'Recommended', command: 'brew install tmux' },
|
|
],
|
|
});
|
|
|
|
const summaryButton = [...host.querySelectorAll('button')].find((button) =>
|
|
button.textContent?.includes('tmux is not installed')
|
|
);
|
|
expect(summaryButton).toBeDefined();
|
|
|
|
act(() => {
|
|
summaryButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
});
|
|
|
|
expect(host.textContent).toContain('brew install tmux');
|
|
expect(host.textContent).not.toContain('Show setup steps');
|
|
|
|
act(() => {
|
|
root.unmount();
|
|
});
|
|
});
|
|
|
|
it('auto-expands when installer flow becomes active', async () => {
|
|
const { host, root } = renderBanner(baseViewModel);
|
|
|
|
mockUseTmuxInstallerBanner.mockReturnValue({
|
|
viewModel: {
|
|
...baseViewModel,
|
|
title: 'tmux needs a restart',
|
|
body: 'Restart Windows before continuing.',
|
|
phase: 'needs_restart',
|
|
progressPercent: 96,
|
|
},
|
|
install: vi.fn(),
|
|
cancel: vi.fn(),
|
|
submitInput: vi.fn(),
|
|
refresh: vi.fn(),
|
|
toggleDetails: vi.fn(),
|
|
openExternal: vi.fn(),
|
|
});
|
|
|
|
await act(async () => {
|
|
root.render(React.createElement(TmuxInstallerBannerView));
|
|
await Promise.resolve();
|
|
});
|
|
|
|
expect(host.textContent).toContain('Restart Windows before continuing.');
|
|
expect(host.textContent).toContain('96%');
|
|
|
|
act(() => {
|
|
root.unmount();
|
|
});
|
|
});
|
|
});
|