|
@@ -1,47 +1,317 @@
|
|
|
import { http, HttpResponse } from 'msw'
|
|
import { http, HttpResponse } from 'msw'
|
|
|
|
|
+import { PatternMetadata, PreviewData } from '@/lib/types'
|
|
|
|
|
|
|
|
-// Default mock data
|
|
|
|
|
-const mockPatterns = [
|
|
|
|
|
- { name: 'star.thr', path: 'patterns/star.thr' },
|
|
|
|
|
- { name: 'spiral.thr', path: 'patterns/spiral.thr' },
|
|
|
|
|
-]
|
|
|
|
|
|
|
+// ============================================
|
|
|
|
|
+// Mock Data Store (mutable for test scenarios)
|
|
|
|
|
+// ============================================
|
|
|
|
|
+
|
|
|
|
|
+export const mockData = {
|
|
|
|
|
+ patterns: [
|
|
|
|
|
+ { path: 'patterns/star.thr', name: 'star.thr', category: 'geometric', date_modified: Date.now(), coordinates_count: 150 },
|
|
|
|
|
+ { path: 'patterns/spiral.thr', name: 'spiral.thr', category: 'organic', date_modified: Date.now() - 86400000, coordinates_count: 200 },
|
|
|
|
|
+ { path: 'patterns/wave.thr', name: 'wave.thr', category: 'organic', date_modified: Date.now() - 172800000, coordinates_count: 175 },
|
|
|
|
|
+ { path: 'patterns/custom/my_design.thr', name: 'my_design.thr', category: 'custom', date_modified: Date.now() - 259200000, coordinates_count: 100 },
|
|
|
|
|
+ ] as PatternMetadata[],
|
|
|
|
|
|
|
|
-const mockPlaylists = ['default', 'favorites']
|
|
|
|
|
|
|
+ playlists: {
|
|
|
|
|
+ default: ['patterns/star.thr', 'patterns/spiral.thr'],
|
|
|
|
|
+ favorites: ['patterns/star.thr'],
|
|
|
|
|
+ geometric: ['patterns/star.thr', 'patterns/wave.thr'],
|
|
|
|
|
+ } as Record<string, string[]>,
|
|
|
|
|
+
|
|
|
|
|
+ status: {
|
|
|
|
|
+ is_running: false,
|
|
|
|
|
+ is_paused: false,
|
|
|
|
|
+ current_file: null as string | null,
|
|
|
|
|
+ speed: 100,
|
|
|
|
|
+ progress: 0,
|
|
|
|
|
+ playlist_mode: false,
|
|
|
|
|
+ playlist_name: null as string | null,
|
|
|
|
|
+ queue: [] as string[],
|
|
|
|
|
+ connection_status: 'connected',
|
|
|
|
|
+ theta: 0,
|
|
|
|
|
+ rho: 0.5,
|
|
|
|
|
+ },
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const mockStatus = {
|
|
|
|
|
- is_running: false,
|
|
|
|
|
- is_paused: false,
|
|
|
|
|
- current_file: null,
|
|
|
|
|
- speed: 100,
|
|
|
|
|
- connection_status: 'connected',
|
|
|
|
|
|
|
+// Reset mock data between tests
|
|
|
|
|
+export function resetMockData() {
|
|
|
|
|
+ mockData.patterns = [
|
|
|
|
|
+ { path: 'patterns/star.thr', name: 'star.thr', category: 'geometric', date_modified: Date.now(), coordinates_count: 150 },
|
|
|
|
|
+ { path: 'patterns/spiral.thr', name: 'spiral.thr', category: 'organic', date_modified: Date.now() - 86400000, coordinates_count: 200 },
|
|
|
|
|
+ { path: 'patterns/wave.thr', name: 'wave.thr', category: 'organic', date_modified: Date.now() - 172800000, coordinates_count: 175 },
|
|
|
|
|
+ { path: 'patterns/custom/my_design.thr', name: 'my_design.thr', category: 'custom', date_modified: Date.now() - 259200000, coordinates_count: 100 },
|
|
|
|
|
+ ]
|
|
|
|
|
+ mockData.playlists = {
|
|
|
|
|
+ default: ['patterns/star.thr', 'patterns/spiral.thr'],
|
|
|
|
|
+ favorites: ['patterns/star.thr'],
|
|
|
|
|
+ geometric: ['patterns/star.thr', 'patterns/wave.thr'],
|
|
|
|
|
+ }
|
|
|
|
|
+ mockData.status = {
|
|
|
|
|
+ is_running: false,
|
|
|
|
|
+ is_paused: false,
|
|
|
|
|
+ current_file: null,
|
|
|
|
|
+ speed: 100,
|
|
|
|
|
+ progress: 0,
|
|
|
|
|
+ playlist_mode: false,
|
|
|
|
|
+ playlist_name: null,
|
|
|
|
|
+ queue: [],
|
|
|
|
|
+ connection_status: 'connected',
|
|
|
|
|
+ theta: 0,
|
|
|
|
|
+ rho: 0.5,
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// ============================================
|
|
|
|
|
+// Handlers
|
|
|
|
|
+// ============================================
|
|
|
|
|
+
|
|
|
export const handlers = [
|
|
export const handlers = [
|
|
|
- // Pattern endpoints
|
|
|
|
|
|
|
+ // ----------------
|
|
|
|
|
+ // Pattern Endpoints
|
|
|
|
|
+ // ----------------
|
|
|
http.get('/list_theta_rho_files', () => {
|
|
http.get('/list_theta_rho_files', () => {
|
|
|
- return HttpResponse.json(mockPatterns)
|
|
|
|
|
|
|
+ return HttpResponse.json(mockData.patterns.map(p => ({ name: p.name, path: p.path })))
|
|
|
}),
|
|
}),
|
|
|
|
|
|
|
|
http.get('/list_theta_rho_files_with_metadata', () => {
|
|
http.get('/list_theta_rho_files_with_metadata', () => {
|
|
|
- return HttpResponse.json(mockPatterns.map(p => ({ ...p, metadata: {} })))
|
|
|
|
|
|
|
+ return HttpResponse.json(mockData.patterns)
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/preview_thr_batch', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { files: string[] }
|
|
|
|
|
+ const previews: Record<string, PreviewData> = {}
|
|
|
|
|
+ for (const file of body.files) {
|
|
|
|
|
+ previews[file] = {
|
|
|
|
|
+ image_data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
|
|
|
|
+ first_coordinate: { x: 0, y: 0 },
|
|
|
|
|
+ last_coordinate: { x: 100, y: 100 },
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return HttpResponse.json(previews)
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/get_theta_rho_coordinates', async () => {
|
|
|
|
|
+ // Return mock coordinates for pattern preview
|
|
|
|
|
+ return HttpResponse.json({
|
|
|
|
|
+ coordinates: Array.from({ length: 50 }, (_, i) => ({
|
|
|
|
|
+ theta: i * 7.2,
|
|
|
|
|
+ rho: 0.5 + Math.sin(i * 0.2) * 0.3,
|
|
|
|
|
+ })),
|
|
|
|
|
+ })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/run_theta_rho', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { file_name?: string; file?: string; pre_execution?: string }
|
|
|
|
|
+ const file = body.file_name || body.file
|
|
|
|
|
+ mockData.status.is_running = true
|
|
|
|
|
+ mockData.status.current_file = file || null
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/delete_theta_rho_file', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { file_path: string }
|
|
|
|
|
+ mockData.patterns = mockData.patterns.filter(p => p.path !== body.file_path)
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/upload_theta_rho', async () => {
|
|
|
|
|
+ return HttpResponse.json({ success: true, path: 'patterns/custom/uploaded.thr' })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.get('/api/pattern_history_all', () => {
|
|
|
|
|
+ return HttpResponse.json({})
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.get('/api/pattern_history/:path', () => {
|
|
|
|
|
+ return HttpResponse.json({ executions: [] })
|
|
|
}),
|
|
}),
|
|
|
|
|
|
|
|
- // Playlist endpoints
|
|
|
|
|
|
|
+ // ----------------
|
|
|
|
|
+ // Playlist Endpoints
|
|
|
|
|
+ // ----------------
|
|
|
http.get('/list_all_playlists', () => {
|
|
http.get('/list_all_playlists', () => {
|
|
|
- return HttpResponse.json(mockPlaylists)
|
|
|
|
|
|
|
+ return HttpResponse.json(Object.keys(mockData.playlists))
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.get('/get_playlist', ({ request }) => {
|
|
|
|
|
+ const url = new URL(request.url)
|
|
|
|
|
+ const name = url.searchParams.get('name')
|
|
|
|
|
+ if (name && mockData.playlists[name]) {
|
|
|
|
|
+ return HttpResponse.json({ name, files: mockData.playlists[name] })
|
|
|
|
|
+ }
|
|
|
|
|
+ return HttpResponse.json({ name: name || '', files: [] })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/create_playlist', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { name: string; files?: string[] }
|
|
|
|
|
+ mockData.playlists[body.name] = body.files || []
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/modify_playlist', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { name: string; files: string[] }
|
|
|
|
|
+ if (mockData.playlists[body.name]) {
|
|
|
|
|
+ mockData.playlists[body.name] = body.files
|
|
|
|
|
+ }
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/rename_playlist', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { old_name: string; new_name: string }
|
|
|
|
|
+ if (mockData.playlists[body.old_name]) {
|
|
|
|
|
+ mockData.playlists[body.new_name] = mockData.playlists[body.old_name]
|
|
|
|
|
+ delete mockData.playlists[body.old_name]
|
|
|
|
|
+ }
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.delete('/delete_playlist', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { name: string }
|
|
|
|
|
+ delete mockData.playlists[body.name]
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/run_playlist', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { name: string }
|
|
|
|
|
+ const playlist = mockData.playlists[body.name]
|
|
|
|
|
+ if (playlist && playlist.length > 0) {
|
|
|
|
|
+ mockData.status.is_running = true
|
|
|
|
|
+ mockData.status.playlist_mode = true
|
|
|
|
|
+ mockData.status.playlist_name = body.name
|
|
|
|
|
+ mockData.status.current_file = playlist[0]
|
|
|
|
|
+ mockData.status.queue = playlist.slice(1)
|
|
|
|
|
+ }
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
}),
|
|
}),
|
|
|
|
|
|
|
|
- // Status endpoint
|
|
|
|
|
|
|
+ http.post('/reorder_playlist', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { from_index: number; to_index: number }
|
|
|
|
|
+ // Reorder the current queue
|
|
|
|
|
+ const queue = [...mockData.status.queue]
|
|
|
|
|
+ const [item] = queue.splice(body.from_index, 1)
|
|
|
|
|
+ queue.splice(body.to_index, 0, item)
|
|
|
|
|
+ mockData.status.queue = queue
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/add_to_playlist', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { playlist_name: string; file_path: string }
|
|
|
|
|
+ if (!mockData.playlists[body.playlist_name]) {
|
|
|
|
|
+ mockData.playlists[body.playlist_name] = []
|
|
|
|
|
+ }
|
|
|
|
|
+ mockData.playlists[body.playlist_name].push(body.file_path)
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/add_to_queue', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { file: string; position?: 'next' | 'end' }
|
|
|
|
|
+ if (body.position === 'next') {
|
|
|
|
|
+ mockData.status.queue.unshift(body.file)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ mockData.status.queue.push(body.file)
|
|
|
|
|
+ }
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ // ----------------
|
|
|
|
|
+ // Playback Control Endpoints
|
|
|
|
|
+ // ----------------
|
|
|
|
|
+ http.post('/pause_execution', () => {
|
|
|
|
|
+ mockData.status.is_paused = true
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/resume_execution', () => {
|
|
|
|
|
+ mockData.status.is_paused = false
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/stop_execution', () => {
|
|
|
|
|
+ mockData.status.is_running = false
|
|
|
|
|
+ mockData.status.is_paused = false
|
|
|
|
|
+ mockData.status.current_file = null
|
|
|
|
|
+ mockData.status.playlist_mode = false
|
|
|
|
|
+ mockData.status.queue = []
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/force_stop', () => {
|
|
|
|
|
+ mockData.status.is_running = false
|
|
|
|
|
+ mockData.status.is_paused = false
|
|
|
|
|
+ mockData.status.current_file = null
|
|
|
|
|
+ mockData.status.playlist_mode = false
|
|
|
|
|
+ mockData.status.queue = []
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/skip_pattern', () => {
|
|
|
|
|
+ if (mockData.status.queue.length > 0) {
|
|
|
|
|
+ mockData.status.current_file = mockData.status.queue.shift() || null
|
|
|
|
|
+ } else {
|
|
|
|
|
+ mockData.status.is_running = false
|
|
|
|
|
+ mockData.status.current_file = null
|
|
|
|
|
+ }
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/set_speed', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { speed: number }
|
|
|
|
|
+ mockData.status.speed = body.speed
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ // ----------------
|
|
|
|
|
+ // Table Control Endpoints
|
|
|
|
|
+ // ----------------
|
|
|
|
|
+ http.post('/send_home', () => {
|
|
|
|
|
+ mockData.status.theta = 0
|
|
|
|
|
+ mockData.status.rho = 0
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/soft_reset', () => {
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/move_to_center', () => {
|
|
|
|
|
+ mockData.status.rho = 0
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/move_to_perimeter', () => {
|
|
|
|
|
+ mockData.status.rho = 1
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.post('/send_coordinate', async ({ request }) => {
|
|
|
|
|
+ const body = await request.json() as { theta: number; rho: number }
|
|
|
|
|
+ mockData.status.theta = body.theta
|
|
|
|
|
+ mockData.status.rho = body.rho
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ // ----------------
|
|
|
|
|
+ // Status Endpoints
|
|
|
|
|
+ // ----------------
|
|
|
http.get('/serial_status', () => {
|
|
http.get('/serial_status', () => {
|
|
|
- return HttpResponse.json(mockStatus)
|
|
|
|
|
|
|
+ return HttpResponse.json(mockData.status)
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ http.get('/list_serial_ports', () => {
|
|
|
|
|
+ return HttpResponse.json(['/dev/ttyUSB0', '/dev/ttyUSB1'])
|
|
|
|
|
+ }),
|
|
|
|
|
+
|
|
|
|
|
+ // Debug serial endpoints
|
|
|
|
|
+ http.post('/api/debug-serial/open', () => {
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
}),
|
|
}),
|
|
|
|
|
|
|
|
- // API prefix versions
|
|
|
|
|
- http.get('/api/patterns', () => {
|
|
|
|
|
- return HttpResponse.json(mockPatterns)
|
|
|
|
|
|
|
+ http.post('/api/debug-serial/close', () => {
|
|
|
|
|
+ return HttpResponse.json({ success: true })
|
|
|
}),
|
|
}),
|
|
|
|
|
|
|
|
- http.get('/api/status', () => {
|
|
|
|
|
- return HttpResponse.json(mockStatus)
|
|
|
|
|
|
|
+ http.post('/api/debug-serial/send', () => {
|
|
|
|
|
+ return HttpResponse.json({ success: true, response: 'OK' })
|
|
|
}),
|
|
}),
|
|
|
]
|
|
]
|