浏览代码

test(03-01): add pattern flow integration tests

- Test browse -> select -> run pattern flow
- Verify API call (run_theta_rho) with correct file parameter
- Test search -> filter -> run filtered pattern
- Verify mock state updates after pattern starts
- Verify API call logging with timestamp and method
tuanchris 1 周之前
父节点
当前提交
286e40d569
共有 1 个文件被更改,包括 218 次插入0 次删除
  1. 218 0
      frontend/src/__tests__/integration/patternFlow.test.tsx

+ 218 - 0
frontend/src/__tests__/integration/patternFlow.test.tsx

@@ -0,0 +1,218 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { renderWithProviders, screen, waitFor, userEvent } from '../../test/utils'
+import { mockData, apiCallLog, resetApiCallLog } from '../../test/mocks/handlers'
+import { BrowsePage } from '../../pages/BrowsePage'
+
+describe('Pattern Flow Integration', () => {
+  beforeEach(() => {
+    vi.clearAllMocks()
+    resetApiCallLog()
+  })
+
+  describe('Browse -> Select -> Run Flow', () => {
+    it('displays pattern list from API', async () => {
+      renderWithProviders(<BrowsePage />)
+
+      await waitFor(() => {
+        expect(screen.getByText('star.thr')).toBeInTheDocument()
+        expect(screen.getByText('spiral.thr')).toBeInTheDocument()
+      })
+    })
+
+    it('opens pattern detail when clicking pattern card', async () => {
+      const user = userEvent.setup()
+      renderWithProviders(<BrowsePage />)
+
+      await waitFor(() => {
+        expect(screen.getByText('star.thr')).toBeInTheDocument()
+      })
+
+      // Click on pattern card
+      await user.click(screen.getByText('star.thr'))
+
+      // Detail sheet should open - pattern name appears twice (grid + sheet title)
+      await waitFor(() => {
+        const patternNames = screen.getAllByText('star.thr')
+        expect(patternNames.length).toBeGreaterThan(1)
+      })
+    })
+
+    it('runs pattern and verifies API call with correct file', async () => {
+      const user = userEvent.setup()
+      renderWithProviders(<BrowsePage />)
+
+      // Wait for patterns to load
+      await waitFor(() => {
+        expect(screen.getByText('star.thr')).toBeInTheDocument()
+      })
+
+      // Pre-condition: not running
+      expect(mockData.status.is_running).toBe(false)
+
+      // Click pattern to open detail sheet
+      await user.click(screen.getByText('star.thr'))
+
+      // Wait for sheet to open and find the main Play button (lg size, not the smaller ones)
+      await waitFor(() => {
+        // The main Play button has "Play" text and play_arrow icon
+        const buttons = screen.getAllByRole('button')
+        const playButton = buttons.find(btn =>
+          btn.textContent?.trim() === 'Play' ||
+          (btn.textContent?.includes('Play') && !btn.textContent?.includes('Next') && !btn.textContent?.includes('Queue'))
+        )
+        expect(playButton).toBeTruthy()
+      })
+
+      // Click the main Play button
+      const buttons = screen.getAllByRole('button')
+      const playButton = buttons.find(btn =>
+        btn.textContent?.trim() === 'Play' ||
+        (btn.textContent?.includes('Play') && !btn.textContent?.includes('Next') && !btn.textContent?.includes('Queue'))
+      )
+      await user.click(playButton!)
+
+      // Verify API was called with correct file
+      await waitFor(() => {
+        const runCall = apiCallLog.find(c => c.endpoint === '/run_theta_rho')
+        expect(runCall).toBeDefined()
+        expect(runCall?.body).toMatchObject({
+          file_name: expect.stringContaining('star')
+        })
+      })
+
+      // Verify mock state was updated
+      expect(mockData.status.is_running).toBe(true)
+      expect(mockData.status.current_file).toContain('star')
+    })
+
+    it('updates mock state after pattern starts running', async () => {
+      const user = userEvent.setup()
+      renderWithProviders(<BrowsePage />)
+
+      await waitFor(() => {
+        expect(screen.getByText('star.thr')).toBeInTheDocument()
+      })
+
+      // Pre-condition: not running
+      expect(mockData.status.is_running).toBe(false)
+
+      // Click pattern to open detail
+      await user.click(screen.getByText('star.thr'))
+
+      // Wait for sheet to open and find main Play button
+      await waitFor(() => {
+        const buttons = screen.getAllByRole('button')
+        const playButton = buttons.find(btn =>
+          btn.textContent?.trim() === 'Play' ||
+          (btn.textContent?.includes('Play') && !btn.textContent?.includes('Next') && !btn.textContent?.includes('Queue'))
+        )
+        expect(playButton).toBeTruthy()
+      })
+
+      // Click the main Play button
+      const buttons = screen.getAllByRole('button')
+      const playButton = buttons.find(btn =>
+        btn.textContent?.trim() === 'Play' ||
+        (btn.textContent?.includes('Play') && !btn.textContent?.includes('Next') && !btn.textContent?.includes('Queue'))
+      )
+      await user.click(playButton!)
+
+      // Post-condition: running
+      await waitFor(() => {
+        expect(mockData.status.is_running).toBe(true)
+      })
+    })
+  })
+
+  describe('Search -> Filter -> Run Flow', () => {
+    it('filters patterns by search then runs filtered result', async () => {
+      const user = userEvent.setup()
+      renderWithProviders(<BrowsePage />)
+
+      await waitFor(() => {
+        expect(screen.getByText('star.thr')).toBeInTheDocument()
+        expect(screen.getByText('spiral.thr')).toBeInTheDocument()
+      })
+
+      // Search for "spiral"
+      const searchInput = screen.getByPlaceholderText(/search/i)
+      await user.type(searchInput, 'spiral')
+
+      // Only spiral should be visible
+      await waitFor(() => {
+        expect(screen.getByText('spiral.thr')).toBeInTheDocument()
+        expect(screen.queryByText('star.thr')).not.toBeInTheDocument()
+      })
+
+      // Click and run the filtered pattern
+      await user.click(screen.getByText('spiral.thr'))
+
+      // Wait for sheet and find main Play button
+      await waitFor(() => {
+        const buttons = screen.getAllByRole('button')
+        const playButton = buttons.find(btn =>
+          btn.textContent?.trim() === 'Play' ||
+          (btn.textContent?.includes('Play') && !btn.textContent?.includes('Next') && !btn.textContent?.includes('Queue'))
+        )
+        expect(playButton).toBeTruthy()
+      })
+
+      // Click main Play button
+      const buttons = screen.getAllByRole('button')
+      const playButton = buttons.find(btn =>
+        btn.textContent?.trim() === 'Play' ||
+        (btn.textContent?.includes('Play') && !btn.textContent?.includes('Next') && !btn.textContent?.includes('Queue'))
+      )
+      await user.click(playButton!)
+
+      // Verify correct pattern was run
+      await waitFor(() => {
+        const runCall = apiCallLog.find(c => c.endpoint === '/run_theta_rho')
+        expect(runCall?.body).toMatchObject({
+          file_name: expect.stringContaining('spiral')
+        })
+      })
+    })
+  })
+
+  describe('API Call Verification', () => {
+    it('logs API call with timestamp and method', async () => {
+      const user = userEvent.setup()
+      renderWithProviders(<BrowsePage />)
+
+      await waitFor(() => {
+        expect(screen.getByText('star.thr')).toBeInTheDocument()
+      })
+
+      // Run a pattern
+      await user.click(screen.getByText('star.thr'))
+
+      // Wait for sheet and find main Play button
+      await waitFor(() => {
+        const buttons = screen.getAllByRole('button')
+        const playButton = buttons.find(btn =>
+          btn.textContent?.trim() === 'Play' ||
+          (btn.textContent?.includes('Play') && !btn.textContent?.includes('Next') && !btn.textContent?.includes('Queue'))
+        )
+        expect(playButton).toBeTruthy()
+      })
+
+      // Click main Play button
+      const allButtons = screen.getAllByRole('button')
+      const mainPlayButton = allButtons.find(btn =>
+        btn.textContent?.trim() === 'Play' ||
+        (btn.textContent?.includes('Play') && !btn.textContent?.includes('Next') && !btn.textContent?.includes('Queue'))
+      )
+      await user.click(mainPlayButton!)
+
+      // Verify API call log structure
+      await waitFor(() => {
+        const runCall = apiCallLog.find(c => c.endpoint === '/run_theta_rho')
+        expect(runCall).toBeDefined()
+        expect(runCall?.method).toBe('POST')
+        expect(runCall?.timestamp).toBeDefined()
+        expect(typeof runCall?.timestamp).toBe('number')
+      })
+    })
+  })
+})