tests(ui): NavigationApi

This commit is contained in:
psychedelicious
2025-07-04 15:26:03 +10:00
parent 9a49682f60
commit 6bc6a680cf

View File

@@ -1,7 +1,8 @@
import type { IDockviewPanel, IGridviewPanel } from 'dockview';
import { GridviewPanel, type IDockviewPanel } from 'dockview';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { NavigationApi } from './navigation-api';
import { LEFT_PANEL_MIN_SIZE_PX, RIGHT_PANEL_MIN_SIZE_PX } from './shared';
// Mock the logger
vi.mock('app/logging/logger', () => ({
@@ -13,13 +14,36 @@ vi.mock('app/logging/logger', () => ({
}),
}));
// Mock panel with setActive method
const createMockPanel = () =>
({
api: {
vi.mock('dockview', async () => {
const actual = await vi.importActual('dockview');
// Mock GridviewPanel class for instanceof checks
class MockGridviewPanel {
maximumWidth: number;
minimumWidth: number;
api = {
setActive: vi.fn(),
},
}) as unknown as IGridviewPanel;
setConstraints: vi.fn(),
setSize: vi.fn(),
};
constructor(config: { maximumWidth?: number; minimumWidth?: number } = {}) {
this.maximumWidth = config.maximumWidth ?? Number.MAX_SAFE_INTEGER;
this.minimumWidth = config.minimumWidth ?? 0;
}
}
return {
...actual,
GridviewPanel: MockGridviewPanel,
};
});
// Mock panel with setActive method
const createMockPanel = (config: { maximumWidth?: number; minimumWidth?: number } = {}) => {
/* @ts-expect-error we are mocking GridviewPanel to be a concrete class */
return new GridviewPanel(config);
};
const createMockDockPanel = () =>
({
@@ -365,4 +389,504 @@ describe('AppNavigationApi', () => {
expect(mockPanel.api.setActive).toHaveBeenCalledOnce();
});
});
describe('Tab Validation', () => {
it('should reject non-enabled tabs in waitForPanel', async () => {
await expect(navigationApi.waitForPanel('models', 'settings')).rejects.toThrow(
'Tab models is not enabled for panel registration'
);
});
it('should reject non-enabled tabs in focusPanel', async () => {
const result = await navigationApi.focusPanel('models', 'settings');
expect(result).toBe(false);
});
it('should warn for non-enabled tabs in getPanel', () => {
const result = navigationApi.getPanel('models', 'settings');
expect(result).toBeUndefined();
});
it('should accept all enabled tabs', async () => {
const enabledTabs = ['canvas', 'generate', 'workflows', 'upscaling'] as const;
for (const tab of enabledTabs) {
// These should timeout since panels aren't registered, but not reject due to invalid tab
await expect(navigationApi.waitForPanel(tab, 'settings', 50)).rejects.toThrow('timed out');
}
});
});
describe('focusPanelInActiveTab', () => {
beforeEach(() => {
navigationApi.connectToApp({ setAppTab: mockSetAppTab, getAppTab: mockGetAppTab });
});
it('should focus panel in active tab', async () => {
const mockPanel = createMockPanel();
navigationApi.registerPanel('generate', 'settings', mockPanel);
mockGetAppTab.mockReturnValue('generate');
const result = await navigationApi.focusPanelInActiveTab('settings');
expect(result).toBe(true);
expect(mockPanel.api.setActive).toHaveBeenCalledOnce();
});
it('should return false when no active tab', async () => {
mockGetAppTab.mockReturnValue(null);
const result = await navigationApi.focusPanelInActiveTab('settings');
expect(result).toBe(false);
});
it('should work without app connection', async () => {
navigationApi.disconnectFromApp();
const result = await navigationApi.focusPanelInActiveTab('settings');
expect(result).toBe(false);
});
});
describe('Panel Expansion and Collapse', () => {
it('should expand panel with correct constraints and size', () => {
const mockPanel = createMockPanel();
const width = 500;
navigationApi.expandPanel(mockPanel, width);
expect(mockPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: width,
});
expect(mockPanel.api.setSize).toHaveBeenCalledWith({ width });
});
it('should collapse panel with zero constraints and size', () => {
const mockPanel = createMockPanel();
navigationApi.collapsePanel(mockPanel);
expect(mockPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: 0,
minimumWidth: 0,
});
expect(mockPanel.api.setSize).toHaveBeenCalledWith({ width: 0 });
});
});
describe('getPanel', () => {
it('should return registered panel', () => {
const mockPanel = createMockPanel();
navigationApi.registerPanel('generate', 'settings', mockPanel);
const result = navigationApi.getPanel('generate', 'settings');
expect(result).toBe(mockPanel);
});
it('should return undefined for unregistered panel', () => {
const result = navigationApi.getPanel('generate', 'nonexistent');
expect(result).toBeUndefined();
});
it('should return undefined for non-enabled tab', () => {
const result = navigationApi.getPanel('models', 'settings');
expect(result).toBeUndefined();
});
});
describe('toggleLeftPanel', () => {
beforeEach(() => {
navigationApi.connectToApp({ setAppTab: mockSetAppTab, getAppTab: mockGetAppTab });
});
it('should expand collapsed left panel', () => {
const mockPanel = createMockPanel({ maximumWidth: 0 });
navigationApi.registerPanel('generate', 'left', mockPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftPanel();
expect(result).toBe(true);
expect(mockPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
});
expect(mockPanel.api.setSize).toHaveBeenCalledWith({ width: LEFT_PANEL_MIN_SIZE_PX });
});
it('should collapse expanded left panel', () => {
const mockPanel = createMockPanel({ maximumWidth: Number.MAX_SAFE_INTEGER });
navigationApi.registerPanel('generate', 'left', mockPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftPanel();
expect(result).toBe(true);
expect(mockPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: 0,
minimumWidth: 0,
});
expect(mockPanel.api.setSize).toHaveBeenCalledWith({ width: 0 });
});
it('should return false when no active tab', () => {
mockGetAppTab.mockReturnValue(null);
const result = navigationApi.toggleLeftPanel();
expect(result).toBe(false);
});
it('should return false when left panel not found', () => {
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftPanel();
expect(result).toBe(false);
});
it('should return false when panel is not GridviewPanel', () => {
const mockPanel = createMockDockPanel();
navigationApi.registerPanel('generate', 'left', mockPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftPanel();
expect(result).toBe(false);
});
});
describe('toggleRightPanel', () => {
beforeEach(() => {
navigationApi.connectToApp({ setAppTab: mockSetAppTab, getAppTab: mockGetAppTab });
});
it('should expand collapsed right panel', () => {
const mockPanel = createMockPanel({ maximumWidth: 0 });
navigationApi.registerPanel('generate', 'right', mockPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleRightPanel();
expect(result).toBe(true);
expect(mockPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
});
expect(mockPanel.api.setSize).toHaveBeenCalledWith({ width: RIGHT_PANEL_MIN_SIZE_PX });
});
it('should collapse expanded right panel', () => {
const mockPanel = createMockPanel({ maximumWidth: Number.MAX_SAFE_INTEGER });
navigationApi.registerPanel('generate', 'right', mockPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleRightPanel();
expect(result).toBe(true);
expect(mockPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: 0,
minimumWidth: 0,
});
expect(mockPanel.api.setSize).toHaveBeenCalledWith({ width: 0 });
});
it('should return false when no active tab', () => {
mockGetAppTab.mockReturnValue(null);
const result = navigationApi.toggleRightPanel();
expect(result).toBe(false);
});
it('should return false when right panel not found', () => {
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleRightPanel();
expect(result).toBe(false);
});
it('should return false when panel is not GridviewPanel', () => {
const mockPanel = createMockDockPanel();
navigationApi.registerPanel('generate', 'right', mockPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleRightPanel();
expect(result).toBe(false);
});
});
describe('toggleLeftAndRightPanels', () => {
beforeEach(() => {
navigationApi.connectToApp({ setAppTab: mockSetAppTab, getAppTab: mockGetAppTab });
});
it('should expand both panels when left is collapsed', () => {
const leftPanel = createMockPanel({ maximumWidth: 0 });
const rightPanel = createMockPanel({ maximumWidth: Number.MAX_SAFE_INTEGER });
navigationApi.registerPanel('generate', 'left', leftPanel);
navigationApi.registerPanel('generate', 'right', rightPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftAndRightPanels();
expect(result).toBe(true);
// Both should be expanded
expect(leftPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
});
expect(rightPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
});
});
it('should expand both panels when right is collapsed', () => {
const leftPanel = createMockPanel({ maximumWidth: Number.MAX_SAFE_INTEGER });
const rightPanel = createMockPanel({ maximumWidth: 0 });
navigationApi.registerPanel('generate', 'left', leftPanel);
navigationApi.registerPanel('generate', 'right', rightPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftAndRightPanels();
expect(result).toBe(true);
// Both should be expanded
expect(leftPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
});
expect(rightPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
});
});
it('should collapse both panels when both are expanded', () => {
const leftPanel = createMockPanel({ maximumWidth: Number.MAX_SAFE_INTEGER });
const rightPanel = createMockPanel({ maximumWidth: Number.MAX_SAFE_INTEGER });
navigationApi.registerPanel('generate', 'left', leftPanel);
navigationApi.registerPanel('generate', 'right', rightPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftAndRightPanels();
expect(result).toBe(true);
// Both should be collapsed
expect(leftPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: 0,
minimumWidth: 0,
});
expect(rightPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: 0,
minimumWidth: 0,
});
});
it('should expand both panels when both are collapsed', () => {
const leftPanel = createMockPanel({ maximumWidth: 0 });
const rightPanel = createMockPanel({ maximumWidth: 0 });
navigationApi.registerPanel('generate', 'left', leftPanel);
navigationApi.registerPanel('generate', 'right', rightPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftAndRightPanels();
expect(result).toBe(true);
// Both should be expanded
expect(leftPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
});
expect(rightPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
});
});
it('should return false when no active tab', () => {
mockGetAppTab.mockReturnValue(null);
const result = navigationApi.toggleLeftAndRightPanels();
expect(result).toBe(false);
});
it('should return false when panels not found', () => {
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftAndRightPanels();
expect(result).toBe(false);
});
it('should return false when panels are not GridviewPanels', () => {
const leftPanel = createMockDockPanel();
const rightPanel = createMockDockPanel();
navigationApi.registerPanel('generate', 'left', leftPanel);
navigationApi.registerPanel('generate', 'right', rightPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.toggleLeftAndRightPanels();
expect(result).toBe(false);
});
});
describe('resetLeftAndRightPanels', () => {
beforeEach(() => {
navigationApi.connectToApp({ setAppTab: mockSetAppTab, getAppTab: mockGetAppTab });
});
it('should reset both panels to expanded state', () => {
const leftPanel = createMockPanel({ maximumWidth: 0 });
const rightPanel = createMockPanel({ maximumWidth: 0 });
navigationApi.registerPanel('generate', 'left', leftPanel);
navigationApi.registerPanel('generate', 'right', rightPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.resetLeftAndRightPanels();
expect(result).toBe(true);
// Both should be reset to expanded state
expect(leftPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
});
expect(leftPanel.api.setSize).toHaveBeenCalledWith({ width: LEFT_PANEL_MIN_SIZE_PX });
expect(rightPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
});
expect(rightPanel.api.setSize).toHaveBeenCalledWith({ width: RIGHT_PANEL_MIN_SIZE_PX });
});
it('should return false when no active tab', () => {
mockGetAppTab.mockReturnValue(null);
const result = navigationApi.resetLeftAndRightPanels();
expect(result).toBe(false);
});
it('should return false when panels not found', () => {
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.resetLeftAndRightPanels();
expect(result).toBe(false);
});
it('should return false when panels are not GridviewPanels', () => {
const leftPanel = createMockDockPanel();
const rightPanel = createMockDockPanel();
navigationApi.registerPanel('generate', 'left', leftPanel);
navigationApi.registerPanel('generate', 'right', rightPanel);
mockGetAppTab.mockReturnValue('generate');
const result = navigationApi.resetLeftAndRightPanels();
expect(result).toBe(false);
});
});
describe('Integration Tests', () => {
beforeEach(() => {
navigationApi.connectToApp({ setAppTab: mockSetAppTab, getAppTab: mockGetAppTab });
});
it('should handle complete panel management workflow', async () => {
const leftPanel = createMockPanel({ maximumWidth: Number.MAX_SAFE_INTEGER });
const rightPanel = createMockPanel({ maximumWidth: Number.MAX_SAFE_INTEGER });
const settingsPanel = createMockPanel();
// Register panels
navigationApi.registerPanel('generate', 'left', leftPanel);
navigationApi.registerPanel('generate', 'right', rightPanel);
navigationApi.registerPanel('generate', 'settings', settingsPanel);
mockGetAppTab.mockReturnValue('generate');
// Focus a panel in active tab
const focusResult = await navigationApi.focusPanelInActiveTab('settings');
expect(focusResult).toBe(true);
expect(settingsPanel.api.setActive).toHaveBeenCalled();
// Toggle panels
navigationApi.toggleLeftAndRightPanels(); // Should collapse both
expect(leftPanel.api.setConstraints).toHaveBeenCalledWith({ maximumWidth: 0, minimumWidth: 0 });
expect(rightPanel.api.setConstraints).toHaveBeenCalledWith({ maximumWidth: 0, minimumWidth: 0 });
// Reset panels
navigationApi.resetLeftAndRightPanels(); // Should expand both
expect(leftPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
});
expect(rightPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
});
});
it('should handle tab switching with panel operations', () => {
const generateLeftPanel = createMockPanel({ maximumWidth: Number.MAX_SAFE_INTEGER });
const canvasLeftPanel = createMockPanel({ maximumWidth: 0 });
navigationApi.registerPanel('generate', 'left', generateLeftPanel);
navigationApi.registerPanel('canvas', 'left', canvasLeftPanel);
// Start on generate tab
mockGetAppTab.mockReturnValue('generate');
navigationApi.toggleLeftPanel(); // Should collapse
expect(generateLeftPanel.api.setConstraints).toHaveBeenCalledWith({ maximumWidth: 0, minimumWidth: 0 });
// Switch to canvas tab
mockGetAppTab.mockReturnValue('canvas');
navigationApi.toggleLeftPanel(); // Should expand
expect(canvasLeftPanel.api.setConstraints).toHaveBeenCalledWith({
maximumWidth: Number.MAX_SAFE_INTEGER,
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
});
});
it('should handle error cases gracefully', () => {
mockGetAppTab.mockReturnValue('generate');
// Test operations without panels registered
expect(navigationApi.toggleLeftPanel()).toBe(false);
expect(navigationApi.toggleRightPanel()).toBe(false);
expect(navigationApi.toggleLeftAndRightPanels()).toBe(false);
expect(navigationApi.resetLeftAndRightPanels()).toBe(false);
expect(navigationApi.getPanel('generate', 'nonexistent')).toBeUndefined();
});
it('should handle async error cases gracefully', async () => {
mockGetAppTab.mockReturnValue('generate');
const focusResult = await navigationApi.focusPanelInActiveTab('nonexistent');
expect(focusResult).toBe(false);
});
});
});