feat(canvas): Studio Enhancement Phase 1 & 2 - v2.0 architecture and file structure
Phase 1 - Foundation:
- Add NodeConfigPanelV2 using useSpecStore for AtomizerSpec v2.0 mode
- Deprecate AtomizerCanvas and useCanvasStore with migration docs
- Add VITE_USE_LEGACY_CANVAS env var for emergency fallback
- Enhance NodePalette with collapse support, filtering, exports
- Add drag-drop support to SpecRenderer with default node data
- Setup test infrastructure (Vitest + Playwright configs)
- Add useSpecStore unit tests (15 tests)
Phase 2 - File Structure & Model:
- Create FileStructurePanel with tree view of study files
- Add ModelNodeV2 with collapsible file dependencies
- Add tabbed left sidebar (Components/Files tabs)
- Add GET /api/files/structure/{study_id} backend endpoint
- Auto-expand 1_setup folders in file tree
- Show model file introspection with solver type and expressions
Technical:
- All TypeScript checks pass
- All 15 unit tests pass
- Production build successful
This commit is contained in:
137
atomizer-dashboard/frontend/src/test/setup.ts
Normal file
137
atomizer-dashboard/frontend/src/test/setup.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Vitest Test Setup
|
||||
*
|
||||
* This file runs before each test file to set up the testing environment.
|
||||
*/
|
||||
|
||||
/// <reference types="vitest/globals" />
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import { vi, beforeAll, afterAll, afterEach } from 'vitest';
|
||||
|
||||
// Type for global context
|
||||
declare const global: typeof globalThis;
|
||||
|
||||
// ============================================================================
|
||||
// Mock Browser APIs
|
||||
// ============================================================================
|
||||
|
||||
// Mock ResizeObserver (used by ReactFlow)
|
||||
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
||||
observe: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock IntersectionObserver
|
||||
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
|
||||
observe: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock matchMedia
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation((query: string) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
// Mock scrollTo
|
||||
Element.prototype.scrollTo = vi.fn();
|
||||
window.scrollTo = vi.fn();
|
||||
|
||||
// Mock fetch for API calls
|
||||
global.fetch = vi.fn();
|
||||
|
||||
// ============================================================================
|
||||
// Mock localStorage
|
||||
// ============================================================================
|
||||
|
||||
const localStorageMock = {
|
||||
getItem: vi.fn(),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
length: 0,
|
||||
key: vi.fn(),
|
||||
};
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
|
||||
|
||||
// ============================================================================
|
||||
// Mock WebSocket
|
||||
// ============================================================================
|
||||
|
||||
class MockWebSocket {
|
||||
static readonly CONNECTING = 0;
|
||||
static readonly OPEN = 1;
|
||||
static readonly CLOSING = 2;
|
||||
static readonly CLOSED = 3;
|
||||
|
||||
readonly CONNECTING = 0;
|
||||
readonly OPEN = 1;
|
||||
readonly CLOSING = 2;
|
||||
readonly CLOSED = 3;
|
||||
|
||||
url: string;
|
||||
readyState: number = MockWebSocket.CONNECTING;
|
||||
onopen: ((event: Event) => void) | null = null;
|
||||
onclose: ((event: CloseEvent) => void) | null = null;
|
||||
onmessage: ((event: MessageEvent) => void) | null = null;
|
||||
onerror: ((event: Event) => void) | null = null;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
// Simulate connection after a tick
|
||||
setTimeout(() => {
|
||||
this.readyState = MockWebSocket.OPEN;
|
||||
this.onopen?.(new Event('open'));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
send = vi.fn();
|
||||
close = vi.fn(() => {
|
||||
this.readyState = MockWebSocket.CLOSED;
|
||||
this.onclose?.(new CloseEvent('close'));
|
||||
});
|
||||
}
|
||||
|
||||
global.WebSocket = MockWebSocket as any;
|
||||
|
||||
// ============================================================================
|
||||
// Console Suppression (optional)
|
||||
// ============================================================================
|
||||
|
||||
// Suppress console.error for expected test warnings
|
||||
const originalError = console.error;
|
||||
beforeAll(() => {
|
||||
console.error = (...args: any[]) => {
|
||||
// Suppress React act() warnings
|
||||
if (typeof args[0] === 'string' && args[0].includes('Warning: An update to')) {
|
||||
return;
|
||||
}
|
||||
originalError.call(console, ...args);
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
console.error = originalError;
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Cleanup
|
||||
// ============================================================================
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorageMock.getItem.mockReset();
|
||||
localStorageMock.setItem.mockReset();
|
||||
});
|
||||
Reference in New Issue
Block a user