feat: integrate MSW for API mocking and testing
- Added MSW (Mock Service Worker) for intercepting network requests in development and testing.
- Created mock data and handlers for example API responses.
- Implemented a new ExamplePage component with a button to trigger API requests.
- Updated router to include a route for the ExamplePage.
- Added tests for ExamplePage using Testing Library and Vitest, ensuring proper rendering and API interaction.
- Configured testing setup to clean up after tests and start the MSW server.
- Updated package.json to include necessary dependencies for testing and mocking.
| 比對新檔案 |
| | |
| | | --- |
| | | applyTo: '**/*.{test,spec}.{ts,js,vue}' |
| | | --- |
| | | |
| | | # Testing Best Practices Instructions |
| | | |
| | | ## Reference Documentation |
| | | |
| | | For detailed testing guidelines and background information, refer to: [Testing Guide](../../### Mock Data Guidelines |
| | | |
| | | ### Realistic Test Data |
| | | |
| | | Use realistic, domain-specific data that mirrors production scenarios: |
| | | |
| | | ```typescript |
| | | // ✅ Good - Realistic data |
| | | const customerData = { |
| | | name: '陳小明', |
| | | email: 'chen.xiaoming@example.com', |
| | | phone: '0912-345-678', |
| | | birthDate: '1985/03/15', // ROC format |
| | | policyNumber: 'INS-2024-000123', |
| | | } |
| | | |
| | | // ❌ Bad - Generic test data |
| | | const customerData = { |
| | | name: 'foo', |
| | | email: 'test@test.com', |
| | | phone: '123', |
| | | birthDate: '2000/01/01', |
| | | } |
| | | ``` |
| | | |
| | | ### Avoid Test Data Anti-Patterns |
| | | |
| | | - Don't use overly simple test strings like 'test', 'foo', 'bar' |
| | | - Use domain-relevant data that mirrors production scenarios |
| | | - Include edge cases specific to your business domain |
| | | - Consider using property-based testing for comprehensive input combinations |
| | | - Generate varied test data to catch edge cases production bugs often surface with unusual inputsuide.md) |
| | | For detailed Testing with MSW guidelines and examples, refer to: [Testing with MSW](../../docs/msw-testing-guide.md) |
| | | |
| | | The following instructions provide focused guidance for writing tests in this codebase. |
| | | |
| | | ## Testing Framework & Architecture |
| | | |
| | | Use **Vitest + Vue Testing Library + Pinia Testing** for comprehensive Vue 3 + TypeScript component testing. |
| | | |
| | | ### Testing Stack |
| | | |
| | | - **Vitest**: Primary testing framework with MSW mocking |
| | | - **@testing-library/vue**: Component rendering and interaction testing |
| | | - **@pinia/testing**: Store state management testing with `createTestingPinia` |
| | | - **MSW (Mock Service Worker)**: API response mocking |
| | | - **@testing-library/jest-dom**: Enhanced DOM assertions |
| | | |
| | | ### Testing Philosophy & Golden Rule |
| | | |
| | | **Design for lean testing**: Tests should be extremely simple, short, flat, and delightful to work with. A test should be readable at first glance and require minimal mental effort to understand its purpose. Avoid complex test logic that competes with your main application code for mental bandwidth. |
| | | |
| | | ## Core Testing Principles |
| | | |
| | | ### Test Naming Convention |
| | | |
| | | Each test name should include three parts: |
| | | |
| | | 1. **What is being tested** (e.g., `ProductService.addNewProduct`) |
| | | 2. **Under what scenario** (e.g., "when price is not provided") |
| | | 3. **Expected result** (e.g., "product is not approved") |
| | | |
| | | ```typescript |
| | | // ✅ Good - Descriptive three-part name |
| | | it('should reject product when price is missing and return validation error', async () => { |
| | | // Test implementation |
| | | }) |
| | | |
| | | // ❌ Bad - Vague name |
| | | it('should add product', async () => { |
| | | // Test implementation |
| | | }) |
| | | ``` |
| | | |
| | | ### 3A Pattern (Arrange-Act-Assert) |
| | | |
| | | Always structure tests with clear separation: |
| | | |
| | | ```typescript |
| | | it('should close popup when confirm button clicked', async () => { |
| | | // Arrange - Setup test environment |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | render(Component, { global: { plugins: [testStore] } }) |
| | | |
| | | // Act - Execute the behavior being tested |
| | | const button = await screen.findByText('確認') |
| | | await userEvent.click(button) |
| | | |
| | | // Assert - Verify expected results |
| | | await waitFor(() => { |
| | | expect(screen.queryByText('測試彈窗')).not.toBeInTheDocument() |
| | | }) |
| | | }) |
| | | ``` |
| | | |
| | | ### Testing Philosophy |
| | | |
| | | Key principles: |
| | | |
| | | - **Test user behavior, not implementation details** |
| | | - **One behavior per test case** |
| | | - **Use realistic test data that mirrors actual business scenarios** |
| | | - **Maintain test isolation to prevent state pollution** |
| | | - **Use declarative assertions with BDD-style language** (prefer `expect().toBe()` over complex conditional logic) |
| | | |
| | | ## Pinia Store Testing Patterns |
| | | |
| | | ### Store State Isolation Strategy |
| | | |
| | | Choose between two approaches based on test complexity: |
| | | |
| | | **Option 1: Independent Store Instances (Recommended for <10 tests)** |
| | | |
| | | ```typescript |
| | | it('should work correctly', async () => { |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | render(Component, { |
| | | global: { plugins: [testStore] }, |
| | | }) |
| | | const store = useMyStore() // Get store AFTER render |
| | | }) |
| | | ``` |
| | | |
| | | **Option 2: Shared Store with Cleanup (For larger test suites)** |
| | | |
| | | ```typescript |
| | | const mockedStore = createTestingPinia({ createSpy: vi.fn }) |
| | | |
| | | beforeEach(() => { |
| | | // Reset store state |
| | | mockedStore.state.value = {} |
| | | }) |
| | | ``` |
| | | |
| | | ### Critical Timing Rules |
| | | |
| | | - **ALWAYS get store instances AFTER `render`** |
| | | - **Use store instances from the rendered component context** |
| | | |
| | | ## Async Testing Patterns |
| | | |
| | | ### Query Method Selection |
| | | |
| | | - **`findByText()`**: Wait for element to appear, throws if not found |
| | | - **`queryByText()`**: Immediate query, returns null if not found |
| | | - **`getByText()`**: Immediate query, throws if not found |
| | | |
| | | ### Async Testing Flow |
| | | |
| | | ```typescript |
| | | // 1. Trigger async operation |
| | | store.popup.fire({ title: '測試彈窗', showConfirmButton: true }) |
| | | |
| | | // 2. Wait for interactive elements to appear |
| | | const confirmButton = await screen.findByText('確認') |
| | | |
| | | // 3. Execute user action |
| | | await userEvent.click(confirmButton) |
| | | |
| | | // 4. Wait for result state |
| | | await waitFor(() => { |
| | | expect(screen.queryByText('測試彈窗')).not.toBeInTheDocument() |
| | | }) |
| | | ``` |
| | | |
| | | ### Vue-Specific Timing |
| | | |
| | | - **`nextTick()`**: Wait for Vue's reactive updates |
| | | - **`findBy*()`**: Wait for DOM elements (includes Vue updates + rendering) |
| | | - **`waitFor()`**: Wait for any condition to become true |
| | | |
| | | ## API Testing with MSW |
| | | |
| | | ### Mock Strategy |
| | | |
| | | - Use MSW handlers for API mocking |
| | | - Test both success and error scenarios |
| | | - Mock authentication responses |
| | | - Test network failure scenarios |
| | | |
| | | ### Request Testing Pattern |
| | | |
| | | ```typescript |
| | | // Test API integration |
| | | const { success, data, msg } = await useRequest('/api/endpoint') |
| | | expect(success).toBe(true) |
| | | expect(data).toMatchObject(expectedResponse) |
| | | ``` |
| | | |
| | | ## Form Testing |
| | | |
| | | ### Multi-Step Form Testing |
| | | |
| | | - Test form state persistence |
| | | - Validate form schemas with realistic data |
| | | - Test step navigation and validation |
| | | - Verify form submission handling |
| | | |
| | | ### Form Validation Pattern |
| | | |
| | | ```typescript |
| | | // Test validation with realistic business data |
| | | const validFormData = { |
| | | name: '王小明', |
| | | email: 'test@example.com', |
| | | phone: '0912345678', |
| | | } |
| | | ``` |
| | | |
| | | ## Component Testing Best Practices |
| | | |
| | | ### Test Structure & Organization |
| | | |
| | | Apply at least two levels of describe blocks for clear test structure: |
| | | |
| | | ```typescript |
| | | describe('PolicyCalculator', () => { |
| | | describe('when calculating premium rates', () => { |
| | | it('should return correct rate for standard coverage', () => { |
| | | // Test implementation |
| | | }) |
| | | |
| | | it('should apply discount for long-term customers', () => { |
| | | // Test implementation |
| | | }) |
| | | }) |
| | | |
| | | describe('when validation fails', () => { |
| | | it('should show error message for invalid age', () => { |
| | | // Test implementation |
| | | }) |
| | | }) |
| | | }) |
| | | ``` |
| | | |
| | | ### Role-Based Testing |
| | | |
| | | - Test permission-based access |
| | | - Verify user role functionality |
| | | - Test different user scenarios |
| | | |
| | | ### Business Logic Testing |
| | | |
| | | - Test calculations and computations |
| | | - Verify business rule enforcement |
| | | - Test data processing functions |
| | | - Validate insurance-specific logic (premium calculations, coverage rules) |
| | | |
| | | ## Error Handling & Edge Cases |
| | | |
| | | ### Exception Testing Best Practices |
| | | |
| | | When testing for expected errors, use dedicated assertion methods instead of try-catch blocks: |
| | | |
| | | ```typescript |
| | | // ✅ Good - Use dedicated assertion |
| | | it('should throw validation error when required field is missing', () => { |
| | | expect(() => validatePolicy({})).toThrow('Policy number is required') |
| | | expect(() => validatePolicy({})).toThrow(ValidationError) // Specific error type |
| | | }) |
| | | |
| | | // ❌ Bad - Manual try-catch |
| | | it('should throw validation error when required field is missing', () => { |
| | | let errorThrown = false |
| | | try { |
| | | validatePolicy({}) |
| | | } catch (error) { |
| | | errorThrown = true |
| | | expect(error.message).toBe('Policy number is required') |
| | | } |
| | | expect(errorThrown).toBe(true) |
| | | }) |
| | | ``` |
| | | |
| | | ### Common Test Scenarios |
| | | |
| | | - Network failures during operations |
| | | - Invalid input data combinations |
| | | - Authentication failures and token expiry |
| | | - Concurrent user sessions |
| | | - Browser storage limitations |
| | | - API timeout scenarios |
| | | - Edge cases with Taiwan-specific data (ROC dates, phone formats, etc.) |
| | | |
| | | ## Mock Data Guidelines |
| | | |
| | | ### Realistic Test Data |
| | | |
| | | - Use authentic-looking data that matches your domain |
| | | - Valid data patterns (emails, phone numbers, etc.) |
| | | - Realistic amounts and values |
| | | - Proper date formats |
| | | |
| | | ### Avoid Test Data Anti-Patterns |
| | | |
| | | - Don't use overly simple test strings like 'test', 'foo', 'bar' |
| | | - Use domain-relevant data that mirrors production scenarios |
| | | - Include edge cases specific to your business domain |
| | | |
| | | ## Debugging Test Failures |
| | | |
| | | ### Common Issues & Solutions |
| | | |
| | | - **Element not found**: Use `screen.debug()` to inspect DOM |
| | | - **Timing issues**: Check if using correct `findBy*` vs `queryBy*` |
| | | - **Store state pollution**: Verify store isolation between tests |
| | | - **Async race conditions**: Ensure proper `waitFor` usage |
| | | |
| | | ### Test Environment Debugging |
| | | |
| | | ```typescript |
| | | // Debug DOM state |
| | | console.log(screen.debug()) |
| | | |
| | | // Debug store state |
| | | console.log(store.$state) |
| | | |
| | | // Debug test timing |
| | | await waitFor(() => { |
| | | console.log('Current DOM:', screen.getByTestId('container').innerHTML) |
| | | }) |
| | | ``` |
| | | |
| | | ## Additional Testing Guidelines |
| | | |
| | | ### Test Doubles (Mocks, Stubs, Spies) Best Practices |
| | | |
| | | Use test doubles judiciously and for the right reasons: |
| | | |
| | | ```typescript |
| | | // ✅ Good - Mock external dependencies to test behavior |
| | | it('should send notification email when payment fails', async () => { |
| | | const emailService = vi.fn() |
| | | const paymentService = vi.fn().mockRejectedValue(new Error('Payment failed')) |
| | | |
| | | await processPayment(paymentData, { emailService, paymentService }) |
| | | |
| | | expect(emailService).toHaveBeenCalledWith({ |
| | | to: customer.email, |
| | | subject: 'Payment Failed', |
| | | // ... other expected parameters |
| | | }) |
| | | }) |
| | | |
| | | // ❌ Bad - Mocking internal implementation details |
| | | it('should call internal helper method', () => { |
| | | const spy = vi.spyOn(component, 'privateHelperMethod') |
| | | component.publicMethod() |
| | | expect(spy).toHaveBeenCalled() // Testing implementation, not behavior |
| | | }) |
| | | ``` |
| | | |
| | | - Focus on critical business logic coverage |
| | | |
| | | Remember: Tests should be reliable, maintainable, and provide confidence in the application's functionality while being easy to understand and debug. |
| 比對新檔案 |
| | |
| | | # 🧪 前端測試撰寫準則(Frontend Testing Guide) |
| | | |
| | | ### 參考 |
| | | |
| | | - [javascript-testing-best-practices](https://github.com/goldbergyoni/javascript-testing-best-practices/blob/master/readme-zh-TW.md) |
| | | - [Unit Testing Vue Apps: Tips, Tricks, and Best Practices](https://docs.google.com/presentation/d/1qr0JXF78UbXPmp0thK2uqgxRwU0kXzE0zykAPC0rQsE/edit?pli=1&slide=id.g13111f83864_0_3181#slide=id.g13111f83864_0_3181) |
| | | |
| | | ## 🎯 文件目的 |
| | | |
| | | 此文件旨在建立團隊撰寫測試的共識,提升測試品質、可讀性與可維護性,避免冗餘或難以維護的測試,並促進穩定的開發流程。 |
| | | |
| | | --- |
| | | |
| | | ## ✅ 核心原則 |
| | | |
| | | ### 1. 3A 原則:Arrange - Act - Assert |
| | | |
| | | 撰寫測試時應依照以下三個階段清楚分區: |
| | | |
| | | - **Arrange**:準備測試所需的環境、資料與元件 |
| | | - **Act**:執行被測行為,例如觸發事件、模擬操作 |
| | | - **Assert**:驗證預期結果,確認程式行為正確 |
| | | |
| | | > 建議每段用空行或註解清楚區隔,提高可讀性。 |
| | | |
| | | ```ts |
| | | it('should call onSubmit when button is clicked', async () => { |
| | | // Arrange |
| | | const onSubmit = vi.fn() |
| | | render(MyForm, { props: { onSubmit } }) |
| | | |
| | | // Act |
| | | await fireEvent.click(screen.getByText('Submit')) |
| | | |
| | | // Assert |
| | | expect(onSubmit).toHaveBeenCalled() |
| | | }) |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 🧱 撰寫測試的基本常識 |
| | | |
| | | ### 2. 可讀性優先於抽象化 |
| | | |
| | | - 避免過度抽象測試流程(尤其是 Act 與 Assert) |
| | | - 測試應該讓讀者「一眼看懂在測什麼」 |
| | | |
| | | #### ✅ 可以抽出: |
| | | |
| | | - 重複使用的 `render()` 或 `setup()` 程式 |
| | | - 重複的 mock 資料 |
| | | |
| | | #### ❌ 不建議抽出: |
| | | |
| | | - 特定場景下才會執行的斷言(避免藏在 helper 裡) |
| | | - 整段測試邏輯 |
| | | |
| | | --- |
| | | |
| | | ### 3. 一個測試只驗證一個「行為」 |
| | | |
| | | - 每個 `it()` 應該只測一件事(單一行為) |
| | | - 減少一個測試中同時驗證多個結果,否則錯誤時難以追蹤原因 |
| | | |
| | | --- |
| | | |
| | | ### 4. 測試命名要清楚描述「行為與結果」 |
| | | |
| | | - ❌ `it('should work')` |
| | | - ✅ `it('should disable the button when input is empty')` |
| | | |
| | | --- |
| | | |
| | | ### 5. 測試資料要貼近實際情境 |
| | | |
| | | - 測試用的資料(props、mock response)盡可能模擬真實場景 |
| | | - 減少硬編字串,提升信任度與可維護性 |
| | | |
| | | --- |
| | | |
| | | ### 6. 保持測試綠燈,清理過時測試 |
| | | |
| | | - 若測試已不符合目前規格(產品或邏輯改變): |
| | | - ✅ 應更新測試內容 |
| | | - ✅ 或註記原因後移除 |
| | | |
| | | > 測試若長期失敗而不修,會讓團隊漸漸無視錯誤,破壞測試的信任度。 |
| | | |
| | | --- |
| | | |
| | | ### 7. 僅測「有價值」的行為,不要測框架細節 |
| | | |
| | | - ❌ 不要測某元素是否有特定 class(除非該 class 有功能影響) |
| | | - ✅ 測按鈕點擊是否導致狀態變更或觸發 callback |
| | | |
| | | --- |
| | | |
| | | ### 8. Mock 是為了隔離副作用,不是逃避驗證 |
| | | |
| | | - mock 的目的是控制輸入與排除副作用(例如 API 請求) |
| | | - 不應以 mock 遮蔽應該被測試的邏輯或流程 |
| | | |
| | | --- |
| | | |
| | | ## ⚙️ 測試結構建議 |
| | | |
| | | ```text |
| | | MyComponent/ |
| | | ├── MyComponent.vue |
| | | ├── MyComponent.test.ts <- 測試檔與元件同層 |
| | | ├── __mocks__/ <- 可選:放複雜 mock data |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 🧪 使用 Testing Library 的最佳實踐 |
| | | |
| | | - Queries 的優先順序: |
| | | - 使用者可視的最優先,例如 `getByRole`, `getByLabelText`, `getByPlaceholderText` |
| | | - 有語意的次要,例如 `getByAltText`, `getByTitle` |
| | | - 沒辦法的才用 `getByTestId`。 |
| | | |
| | | - Query Container 的優先順序: screen 最優先,container 次要。 |
| | | |
| | | - 非同步狀態使用 `findBy...` 或 `waitFor`,避免 race condition |
| | | |
| | | - 每個測試結束都要執行 cleanup(),目前已寫在 setupTests.ts 中。 |
| | | |
| | | --- |
| | | |
| | | ## 📌 小提醒清單(Cheatsheet) |
| | | |
| | | | 原則 | 說明 | |
| | | | -------------- | ----------------------------------------- | |
| | | | 3A 原則 | 清楚分區 Arrange / Act / Assert | |
| | | | 可讀性優先 | 測試邏輯應直接閱讀理解 | |
| | | | 適度抽象 | 可抽出 setup,但保留每個場景關鍵的斷言 | |
| | | | 保持綠燈 | 測試要穩定通過,過時就更新或移除 | |
| | | | 單一責任 | 一個 `it()` 測一件事 | |
| | | | 模擬真實 | 使用貼近實際情況的資料與流程 | |
| | | | 測行為 | 測使用者觀點的功能,不測框架或視覺細節 | |
| | | | Store 隔離 | 避免測試間 Pinia store 狀態汙染 | |
| | | | 正確等待 | `findBy*` 等待出現,`queryBy*` 檢查不存在 | |
| | | | 分離操作等待 | 先等待元素出現,再執行操作,最後等待結果 | |
| | | | Store 實例時機 | 在 `render` 後取得 store 實例 | |
| | | |
| | | ### 🔍 Query 方法快速參考 |
| | | |
| | | | 情境 | 使用方法 | 說明 | |
| | | | ---------------------- | ---------------------- | ------------------------------------ | |
| | | | 測試元素存在 | `findByText()` | 等待元素出現,找不到會拋錯(預設一秒) | |
| | | | 測試元素不存在 | `queryByText()` | 立即查詢,找不到返回 null | |
| | | | 立即取得已存在的元素 | `getByText()` | 立即查詢,找不到會拋錯 | |
| | | | 等待元素出現後進行操作 | `await findByText()` | 適合需要等待 DOM 更新的互動元素 | |
| | | | 等待狀態變化 | `waitFor(() => {...})` | 適合等待非同步操作完成後的狀態檢查 | |
| | | |
| | | --- |
| | | |
| | | ## 套件簡介 |
| | | |
| | | | 步驟 | 主要負責套件 | 執行的任務 | 比喻 | |
| | | | :--: | :------------------------ | :------------------------------------------------------------------------------------ | :------------------- | |
| | | | 1 | Vitest | 啟動並管理整個測試流程(Test runner)。 | 導演 / 測試監督 | |
| | | | 2 | jsdom | 在 Node.js 中建立一個模擬的瀏覽器 DOM 環境 (提供 document, window 等)。 | 搭建虛擬舞台 | |
| | | | 3 | @testing-library/vue | 將要測試的元件渲染到 jsdom 所建立的虛擬 DOM 中。 | 演員登台表演 | |
| | | | 4 | @testing-library/jest-dom | 提供語意化的斷言函式(matcher) (如 .toBeInTheDocument()) 來檢查 DOM 狀態是否符合預期。 | 使用專業工具驗收舞台 | |
| | | |
| | | --- |
| | | |
| | | --- |
| | | |
| | | ## 測試小知識 |
| | | |
| | | ### 1. 在 Vue 測試中如何正確等待 DOM 更新與非同步操作 |
| | | |
| | | 在進行 Vue 元件測試時,一個常見的挑戰是處理「非同步」行為。這包括 Vue 本身的 DOM 更新機制 (tick),以及像 API 請求這樣的 Promise。如果沒有正確地等待,測試就可能會在畫面或資料還沒準備好時就進行斷言 (assert),導致測試失敗。 |
| | | |
| | | 以下是正確的處理流程與觀念: |
| | | |
| | | **核心觀念** |
| | | |
| | | 1. Vue 的 Tick 機制 |
| | | - Vue 不會在你每次更改資料時,都「立即」更新畫面 (DOM)。 |
| | | - 相反地,它會將同一個事件循環 (event loop) 中的所有更新打包起來,在「下一個 tick」再一次性地完成。 |
| | | - 這樣做是為了效能,避免不必要的重複渲染。 |
| | | |
| | | 2. 非同步操作 (Promise) |
| | | - 像 axios 或 fetch 這類的 API 請求是非同步的,它們會回傳一個 Promise。 |
| | | - 程式碼不會停下來等它完成,而是會繼續往下執行。測試必須明確地等待這些 Promise 完成。 |
| | | |
| | | **正確的測試流程** |
| | | |
| | | 假設我們要測試元件在錨定 (onMounted) 時會做的兩件事: |
| | | |
| | | 1. 顯示載入狀態。 |
| | | 2. 發送 API 請求去取得資料,結束後載入狀態消失。 |
| | | |
| | | 在 Vue 中測試包含 API 請求的非同步行為時,關鍵在於等待兩個不同的時機點:畫面更新與非同步任務完成。 |
| | | |
| | | 第一步:等待「載入中」狀態出現 |
| | | |
| | | - 當元件渲染並觸發 API 請求後,它會立即更新內部狀態 (例如 isLoading = true),但這個變化不會馬上渲染到畫面上。 |
| | | - 使用 await nextTick() 來暫停測試,並等待 Vue 完成其 DOM 更新週期。 |
| | | - 此時,可以斷言「載入中」的 UI 元素 (如 .el-loading-mask) 已經出現在畫面上。 |
| | | |
| | | 第二步:等待「載入完成」狀態 |
| | | |
| | | - API 請求需要時間來完成。當請求結束後,元件會再次更新狀態 (例如 isLoading = false),並移除「載入中」的 UI。 |
| | | - 使用 await waitFor() 來反覆檢查畫面,直到「載入中」的 UI 元素從畫面上消失為止。 |
| | | - waitFor 會在指定的超時 (timeout) 時間內,持續輪詢檢查您的斷言是否成立,這非常適合用來等待一個非同步操作的結束。 |
| | | |
| | | 總結來說:使用 nextTick 捕捉初始狀態的畫面變化;使用 waitFor 等待非同步操作結束後的最終畫面狀態。 |
| | | |
| | | --- |
| | | |
| | | ## 🏪 Pinia Store 測試最佳實踐 |
| | | |
| | | ### Store 狀態隔離策略 |
| | | |
| | | 在測試使用 Pinia store 的元件時,最重要的考量是**避免測試間的狀態汙染**。以下提供兩種常見的策略,各有優缺點: |
| | | |
| | | #### 策略 1:每個測試獨立建立 Store 實例 |
| | | |
| | | ```typescript |
| | | // ✅ 策略 1:完全隔離,每個測試都建立新的 store |
| | | it('should work correctly', () => { |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | |
| | | render(Component, { |
| | | global: { plugins: [testStore] }, |
| | | }) |
| | | |
| | | const store = useMyStore() // 在 render 後取得 store 實例 |
| | | // 測試邏輯... |
| | | }) |
| | | ``` |
| | | |
| | | **優點**: |
| | | |
| | | - 完全避免測試間汙染 |
| | | - 每個測試都有乾淨的初始狀態 |
| | | - 測試間完全獨立,執行順序不影響結果 |
| | | |
| | | **缺點**: |
| | | |
| | | - 程式碼較冗長,需要重複建立 store |
| | | - 測試執行時間可能稍長 |
| | | |
| | | #### 策略 2:共享 Store 實例 + 清理機制 |
| | | |
| | | ```typescript |
| | | // ✅ 策略 2:共享實例,但在測試間清理狀態 |
| | | const mockedStore = createTestingPinia({ createSpy: vi.fn }) |
| | | |
| | | beforeEach(() => { |
| | | // 選項 A: 重置所有 store 狀態 |
| | | mockedStore.state.value = {} |
| | | |
| | | // 選項 B: 手動重置特定 store |
| | | // const store = useMyStore() |
| | | // store.$reset() 或手動重置特定屬性 |
| | | }) |
| | | |
| | | // 或使用 afterEach |
| | | afterEach(() => { |
| | | // 清理邏輯同上 |
| | | }) |
| | | ``` |
| | | |
| | | **優點**: |
| | | |
| | | - 程式碼較簡潔 |
| | | - 測試執行效率較高 |
| | | - 適合大量測試的場景 |
| | | |
| | | **缺點**: |
| | | |
| | | - 需要確保清理邏輯完整 |
| | | - 如果清理不當,仍可能有狀態汙染 |
| | | - 需要了解 store 的內部結構來正確清理 |
| | | |
| | | #### 建議選擇標準 |
| | | |
| | | - **小型測試檔案**(<10 個測試):建議使用策略 1 |
| | | - **大型測試檔案**或**頻繁使用相同 store 配置**:考慮策略 2 |
| | | - **複雜的 store 狀態**:建議使用策略 1 以避免清理遺漏 |
| | | - **簡單的 store 狀態**:兩種策略都可以 |
| | | |
| | | ### Store 實例取得時機 |
| | | |
| | | ⚠️ **重要**:務必在 `render` 之後才取得 store 實例 |
| | | |
| | | ```typescript |
| | | // ❌ 錯誤:在 render 前取得 store,可能取得錯誤的實例 |
| | | const store = useMyStore() |
| | | render(Component, { global: { plugins: [testStore] } }) |
| | | |
| | | // ✅ 正確:在 render 後取得 store |
| | | render(Component, { global: { plugins: [testStore] } }) |
| | | const store = useMyStore() |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 🔄 非同步測試的常見模式與陷阱 |
| | | |
| | | ### findBy vs queryBy 的選擇 |
| | | |
| | | 了解何時使用不同的查詢方法是成功測試的關鍵: |
| | | |
| | | ```typescript |
| | | // ✅ 測試元素存在:使用 findBy* (會等待元素出現) |
| | | expect(await screen.findByText('載入完成')).toBeInTheDocument() |
| | | |
| | | // ✅ 測試元素不存在:使用 queryBy* (立即查詢,不等待) |
| | | expect(screen.queryByText('已移除')).not.toBeInTheDocument() |
| | | |
| | | // ❌ 錯誤:用 findBy 測試不存在會拋錯 |
| | | expect(await screen.findByText('不存在')).not.toBeInTheDocument() // 會拋錯! |
| | | ``` |
| | | |
| | | ### 非同步操作的正確等待模式 |
| | | |
| | | 當需要等待元素出現後進行操作時: |
| | | |
| | | ```typescript |
| | | // ✅ 推薦模式:分步等待 |
| | | it('should close popup when confirm button clicked', async () => { |
| | | // 1. 觸發顯示彈窗 |
| | | store.popup.fire({ title: '測試彈窗', showConfirmButton: true }) |
| | | |
| | | // 2. 等待互動元素出現 |
| | | const confirmButton = await screen.findByText('確認') |
| | | |
| | | // 3. 執行使用者操作 |
| | | await userEvent.click(confirmButton) |
| | | |
| | | // 4. 等待操作結果 |
| | | await waitFor(() => { |
| | | expect(screen.queryByText('測試彈窗')).not.toBeInTheDocument() |
| | | }) |
| | | }) |
| | | ``` |
| | | |
| | | ### waitFor 的使用原則 |
| | | |
| | | ```typescript |
| | | // ✅ 正確:waitFor 內只做斷言,不執行操作 |
| | | await userEvent.click(button) |
| | | await waitFor(() => { |
| | | expect(element).not.toBeInTheDocument() |
| | | }) |
| | | |
| | | // ⚠️ 特殊情況:當操作目標也需要等待出現時 |
| | | await waitFor(async () => { |
| | | const dynamicButton = screen.getByText('動態按鈕') |
| | | await userEvent.click(dynamicButton) |
| | | expect(result).toBeTruthy() |
| | | }) |
| | | |
| | | // ✅ 更好的方式:使用 findBy 分離等待和操作 |
| | | const dynamicButton = await screen.findByText('動態按鈕') |
| | | await userEvent.click(dynamicButton) |
| | | await waitFor(() => { |
| | | expect(result).toBeTruthy() |
| | | }) |
| | | ``` |
| | | |
| | | ### Vue 特有的等待時機 |
| | | |
| | | ```typescript |
| | | // nextTick:等待 Vue 的響應式更新 |
| | | await nextTick() |
| | | |
| | | // findBy*:等待 DOM 元素出現(包含 Vue 更新 + 渲染) |
| | | const element = await screen.findByText('目標文字') |
| | | |
| | | // waitFor:等待任意條件成立(通常用於狀態變化) |
| | | expect(condition).toBeTruthy() |
| | | }) |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 💡 實際案例:Popup 元件測試 |
| | | |
| | | 以下是一個完整的 Popup 測試案例,展示了本文提到的最佳實踐: |
| | | |
| | | ```typescript |
| | | import { describe, vi, it, expect } from 'vitest' |
| | | import { createTestingPinia } from '@pinia/testing' |
| | | import { screen, waitFor, render } from '@testing-library/vue' |
| | | import userEvent from '@testing-library/user-event' |
| | | import App from '@/App.vue' |
| | | import { usePopupStore } from '@/stores/popup' |
| | | |
| | | vi.mock('vue-router', () => ({ |
| | | useRoute: vi.fn(() => ({ meta: {} })), |
| | | })) |
| | | |
| | | describe('使用者可以互動 popup 並關閉', () => { |
| | | it('使用 popup.fire(config) 應正確依照 config.title 參數顯示彈窗文字', async () => { |
| | | // Arrange - 準備測試環境 |
| | | const mockedStore = createTestingPinia({ createSpy: vi.fn }) |
| | | render(App, { |
| | | global: { plugins: [mockedStore] }, |
| | | }) |
| | | |
| | | // Act - 執行被測行為 |
| | | const store = usePopupStore() // 在 render 後取得 store |
| | | store.popup.fire({ title: '測試彈窗', showConfirmButton: true }) |
| | | |
| | | // Assert - 驗證結果 |
| | | expect(await screen.findByText('測試彈窗')).toBeInTheDocument() |
| | | }) |
| | | |
| | | it('點擊確認按鈕後,會關閉彈窗', async () => { |
| | | // Arrange |
| | | const mockedStore = createTestingPinia({ createSpy: vi.fn }) |
| | | render(App, { |
| | | global: { plugins: [mockedStore] }, |
| | | }) |
| | | const store = usePopupStore() |
| | | |
| | | // Act - 分步執行:顯示彈窗 → 等待按鈕出現 → 點擊 → 等待關閉 |
| | | store.popup.fire({ title: '測試彈窗', showConfirmButton: true }) |
| | | |
| | | const confirmButton = await screen.findByText('確認') // 等待元素出現 |
| | | await userEvent.click(confirmButton) // 執行操作 |
| | | |
| | | // Assert - 等待彈窗消失 |
| | | await waitFor(() => { |
| | | expect(screen.queryByText('測試彈窗')).not.toBeInTheDocument() |
| | | }) |
| | | }) |
| | | }) |
| | | ``` |
| | | |
| | | ### 此案例展示的重點: |
| | | |
| | | 1. **Store 隔離**:每個測試建立獨立的 `createTestingPinia` 實例 |
| | | 2. **正確的實例取得時機**:在 `render` 後才取得 store |
| | | 3. **適當的等待策略**: |
| | | - 使用 `findByText` 等待彈窗標題出現 |
| | | - 使用 `findByText` 等待確認按鈕出現 |
| | | - 使用 `queryByText` + `waitFor` 等待彈窗消失 |
| | | 4. **清晰的 3A 結構**:Arrange、Act、Assert 分明 |
| | | 5. **分離操作和等待**:先等待元素出現,再執行點擊,最後等待結果 |
| | | |
| | | --- |
| 比對新檔案 |
| | |
| | | import { test, expect } from '@playwright/test' |
| | | |
| | | /** |
| | | * ExamplePage - 完整使用者流程測試 |
| | | * |
| | | * 測試目標:驗證使用者從進入頁面到完成 API 請求的完整流程 |
| | | * 測試環境:真實瀏覽器環境,不使用 mock |
| | | */ |
| | | test.describe('ExamplePage - 完整使用者流程', () => { |
| | | test.beforeEach(async ({ page }) => { |
| | | // 導航到 ExamplePage |
| | | await page.goto('/') |
| | | }) |
| | | |
| | | test('使用者應該能看到頁面標題和請求按鈕', async ({ page }) => { |
| | | // Arrange & Act - 頁面已在 beforeEach 中載入 |
| | | |
| | | // Assert - 驗證頁面標題存在(至少出現一次) |
| | | await expect(page.getByText('I am Example Page').first()).toBeVisible() |
| | | |
| | | // Assert - 驗證請求按鈕存在並可見 |
| | | const button = page.getByRole('button', { |
| | | name: /click to call example request/i, |
| | | }) |
| | | await expect(button).toBeVisible() |
| | | }) |
| | | |
| | | test('使用者點擊按鈕後應該看到完整的請求流程:loading → 結果顯示', async ({ page }) => { |
| | | // Arrange - 取得按鈕元素 |
| | | const button = page.getByRole('button', { |
| | | name: /click to call example request/i, |
| | | }) |
| | | |
| | | // Assert - 初始狀態不應該顯示 loading 或結果 |
| | | await expect(page.getByText('fetching...')).toBeHidden() |
| | | await expect(page.getByText('the mock response is:')).toBeHidden() |
| | | |
| | | // Act - 點擊按鈕觸發請求 |
| | | await button.click() |
| | | |
| | | // Assert - 驗證 loading 狀態出現(需要快速檢查,因為可能很快消失) |
| | | // 使用 waitFor 而不是 expect,因為 loading 可能很快就消失 |
| | | try { |
| | | await expect(page.getByText('fetching...')).toBeVisible({ timeout: 1000 }) |
| | | } catch { |
| | | // 如果 API 太快回應,loading 可能已經消失,這是可接受的 |
| | | console.log('Loading state was too fast to capture') |
| | | } |
| | | |
| | | // Assert - 驗證最終結果顯示 |
| | | await expect(page.getByText('the mock response is:')).toBeVisible({ |
| | | timeout: 5000, |
| | | }) |
| | | |
| | | // Assert - 驗證回應資料出現 |
| | | await expect(page.getByText(/hello world/i)).toBeVisible() |
| | | |
| | | // Assert - 驗證 loading 狀態已消失 |
| | | await expect(page.getByText('fetching...')).toBeHidden() |
| | | }) |
| | | |
| | | test('驗證回應資料的格式正確(JSON 字串)', async ({ page }) => { |
| | | // Arrange & Act |
| | | const button = page.getByRole('button', { |
| | | name: /click to call example request/i, |
| | | }) |
| | | await button.click() |
| | | |
| | | // Assert - 等待結果顯示 |
| | | await expect(page.getByText('the mock response is:')).toBeVisible({ |
| | | timeout: 5000, |
| | | }) |
| | | |
| | | // Assert - 驗證資料包含引號(JSON 字串格式) |
| | | const responseText = await page.getByText(/"hello world"/i).textContent() |
| | | expect(responseText).toContain('"') |
| | | expect(responseText).toContain('hello world') |
| | | }) |
| | | }) |
| | |
| | | "version": "0.0.0", |
| | | "dependencies": { |
| | | "@tailwindcss/vite": "^4.1.16", |
| | | "axios": "^1.13.2", |
| | | "pinia": "^3.0.3", |
| | | "tailwind-merge": "^3.4.0", |
| | | "tailwindcss": "^4.1.16", |
| | |
| | | "vue-router": "^4.6.3" |
| | | }, |
| | | "devDependencies": { |
| | | "@pinia/testing": "^1.0.3", |
| | | "@playwright/test": "^1.56.1", |
| | | "@testing-library/jest-dom": "^6.9.1", |
| | | "@testing-library/user-event": "^14.6.1", |
| | | "@testing-library/vue": "^8.1.0", |
| | | "@tsconfig/node22": "^22.0.2", |
| | | "@types/jsdom": "^27.0.0", |
| | | "@types/node": "^22.18.11", |
| | |
| | | "eslint-plugin-vue": "~10.5.0", |
| | | "jiti": "^2.6.1", |
| | | "jsdom": "^27.0.1", |
| | | "msw": "^2.12.1", |
| | | "npm-run-all2": "^8.0.4", |
| | | "prettier": "^3.6.2", |
| | | "prettier-plugin-tailwindcss": "^0.7.1", |
| | |
| | | "version": "0.9.20", |
| | | "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.20.tgz", |
| | | "integrity": "sha512-YUSA5jW8qn/c6nZUlFsn2Nt5qFFRBcGTgL9CzbiZbJCtEFY0Nv/ycO3BHT9tLjus9++zOYWe5mLCRIesuay25g==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@adobe/css-tools": { |
| | | "version": "4.4.4", |
| | | "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", |
| | | "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | |
| | | }, |
| | | "peerDependencies": { |
| | | "@babel/core": "^7.0.0-0" |
| | | } |
| | | }, |
| | | "node_modules/@babel/runtime": { |
| | | "version": "7.28.4", |
| | | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", |
| | | "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=6.9.0" |
| | | } |
| | | }, |
| | | "node_modules/@babel/template": { |
| | |
| | | "url": "https://github.com/sponsors/nzakas" |
| | | } |
| | | }, |
| | | "node_modules/@inquirer/ansi": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", |
| | | "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@inquirer/confirm": { |
| | | "version": "5.1.20", |
| | | "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.20.tgz", |
| | | "integrity": "sha512-HDGiWh2tyRZa0M1ZnEIUCQro25gW/mN8ODByicQrbR1yHx4hT+IOpozCMi5TgBtUdklLwRI2mv14eNpftDluEw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@inquirer/core": "^10.3.1", |
| | | "@inquirer/type": "^3.0.10" |
| | | }, |
| | | "engines": { |
| | | "node": ">=18" |
| | | }, |
| | | "peerDependencies": { |
| | | "@types/node": ">=18" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "@types/node": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/@inquirer/core": { |
| | | "version": "10.3.1", |
| | | "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.1.tgz", |
| | | "integrity": "sha512-hzGKIkfomGFPgxKmnKEKeA+uCYBqC+TKtRx5LgyHRCrF6S2MliwRIjp3sUaWwVzMp7ZXVs8elB0Tfe682Rpg4w==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@inquirer/ansi": "^1.0.2", |
| | | "@inquirer/figures": "^1.0.15", |
| | | "@inquirer/type": "^3.0.10", |
| | | "cli-width": "^4.1.0", |
| | | "mute-stream": "^3.0.0", |
| | | "signal-exit": "^4.1.0", |
| | | "wrap-ansi": "^6.2.0", |
| | | "yoctocolors-cjs": "^2.1.3" |
| | | }, |
| | | "engines": { |
| | | "node": ">=18" |
| | | }, |
| | | "peerDependencies": { |
| | | "@types/node": ">=18" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "@types/node": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/@inquirer/core/node_modules/ansi-regex": { |
| | | "version": "5.0.1", |
| | | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", |
| | | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/@inquirer/core/node_modules/emoji-regex": { |
| | | "version": "8.0.0", |
| | | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", |
| | | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@inquirer/core/node_modules/string-width": { |
| | | "version": "4.2.3", |
| | | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", |
| | | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "emoji-regex": "^8.0.0", |
| | | "is-fullwidth-code-point": "^3.0.0", |
| | | "strip-ansi": "^6.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/@inquirer/core/node_modules/strip-ansi": { |
| | | "version": "6.0.1", |
| | | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", |
| | | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "ansi-regex": "^5.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/@inquirer/core/node_modules/wrap-ansi": { |
| | | "version": "6.2.0", |
| | | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", |
| | | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "ansi-styles": "^4.0.0", |
| | | "string-width": "^4.1.0", |
| | | "strip-ansi": "^6.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/@inquirer/figures": { |
| | | "version": "1.0.15", |
| | | "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", |
| | | "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@inquirer/type": { |
| | | "version": "3.0.10", |
| | | "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", |
| | | "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=18" |
| | | }, |
| | | "peerDependencies": { |
| | | "@types/node": ">=18" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "@types/node": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/@isaacs/cliui": { |
| | | "version": "8.0.2", |
| | | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", |
| | |
| | | "@jridgewell/sourcemap-codec": "^1.4.14" |
| | | } |
| | | }, |
| | | "node_modules/@mswjs/interceptors": { |
| | | "version": "0.40.0", |
| | | "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.40.0.tgz", |
| | | "integrity": "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@open-draft/deferred-promise": "^2.2.0", |
| | | "@open-draft/logger": "^0.3.0", |
| | | "@open-draft/until": "^2.0.0", |
| | | "is-node-process": "^1.2.0", |
| | | "outvariant": "^1.4.3", |
| | | "strict-event-emitter": "^0.5.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@nodelib/fs.scandir": { |
| | | "version": "2.1.5", |
| | | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", |
| | |
| | | "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@open-draft/deferred-promise": { |
| | | "version": "2.2.0", |
| | | "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", |
| | | "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@open-draft/logger": { |
| | | "version": "0.3.0", |
| | | "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", |
| | | "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "is-node-process": "^1.2.0", |
| | | "outvariant": "^1.4.0" |
| | | } |
| | | }, |
| | | "node_modules/@open-draft/until": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", |
| | | "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@pinia/testing": { |
| | | "version": "1.0.3", |
| | | "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-1.0.3.tgz", |
| | | "integrity": "sha512-g+qR49GNdI1Z8rZxKrQC3GN+LfnGTNf5Kk8Nz5Cz6mIGva5WRS+ffPXQfzhA0nu6TveWzPNYTjGl4nJqd3Cu9Q==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/posva" |
| | | }, |
| | | "peerDependencies": { |
| | | "pinia": ">=3.0.4" |
| | | } |
| | | }, |
| | | "node_modules/@pkgjs/parseargs": { |
| | | "version": "0.11.0", |
| | |
| | | "vite": "^5.2.0 || ^6 || ^7" |
| | | } |
| | | }, |
| | | "node_modules/@testing-library/dom": { |
| | | "version": "10.4.1", |
| | | "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", |
| | | "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@babel/code-frame": "^7.10.4", |
| | | "@babel/runtime": "^7.12.5", |
| | | "@types/aria-query": "^5.0.1", |
| | | "aria-query": "5.3.0", |
| | | "dom-accessibility-api": "^0.5.9", |
| | | "lz-string": "^1.5.0", |
| | | "picocolors": "1.1.1", |
| | | "pretty-format": "^27.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@testing-library/jest-dom": { |
| | | "version": "6.9.1", |
| | | "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", |
| | | "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@adobe/css-tools": "^4.4.0", |
| | | "aria-query": "^5.0.0", |
| | | "css.escape": "^1.5.1", |
| | | "dom-accessibility-api": "^0.6.3", |
| | | "picocolors": "^1.1.1", |
| | | "redent": "^3.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=14", |
| | | "npm": ">=6", |
| | | "yarn": ">=1" |
| | | } |
| | | }, |
| | | "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { |
| | | "version": "0.6.3", |
| | | "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", |
| | | "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@testing-library/user-event": { |
| | | "version": "14.6.1", |
| | | "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", |
| | | "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=12", |
| | | "npm": ">=6" |
| | | }, |
| | | "peerDependencies": { |
| | | "@testing-library/dom": ">=7.21.4" |
| | | } |
| | | }, |
| | | "node_modules/@testing-library/vue": { |
| | | "version": "8.1.0", |
| | | "resolved": "https://registry.npmjs.org/@testing-library/vue/-/vue-8.1.0.tgz", |
| | | "integrity": "sha512-ls4RiHO1ta4mxqqajWRh8158uFObVrrtAPoxk7cIp4HrnQUj/ScKzqz53HxYpG3X6Zb7H2v+0eTGLSoy8HQ2nA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@babel/runtime": "^7.23.2", |
| | | "@testing-library/dom": "^9.3.3", |
| | | "@vue/test-utils": "^2.4.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=14" |
| | | }, |
| | | "peerDependencies": { |
| | | "@vue/compiler-sfc": ">= 3", |
| | | "vue": ">= 3" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "@vue/compiler-sfc": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/@testing-library/vue/node_modules/@testing-library/dom": { |
| | | "version": "9.3.4", |
| | | "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", |
| | | "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@babel/code-frame": "^7.10.4", |
| | | "@babel/runtime": "^7.12.5", |
| | | "@types/aria-query": "^5.0.1", |
| | | "aria-query": "5.1.3", |
| | | "chalk": "^4.1.0", |
| | | "dom-accessibility-api": "^0.5.9", |
| | | "lz-string": "^1.5.0", |
| | | "pretty-format": "^27.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">=14" |
| | | } |
| | | }, |
| | | "node_modules/@testing-library/vue/node_modules/aria-query": { |
| | | "version": "5.1.3", |
| | | "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", |
| | | "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", |
| | | "dev": true, |
| | | "license": "Apache-2.0", |
| | | "dependencies": { |
| | | "deep-equal": "^2.0.5" |
| | | } |
| | | }, |
| | | "node_modules/@tsconfig/node22": { |
| | | "version": "22.0.2", |
| | | "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.2.tgz", |
| | | "integrity": "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@types/aria-query": { |
| | | "version": "5.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", |
| | | "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | |
| | | "dependencies": { |
| | | "undici-types": "~6.21.0" |
| | | } |
| | | }, |
| | | "node_modules/@types/statuses": { |
| | | "version": "2.0.6", |
| | | "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", |
| | | "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@types/tough-cookie": { |
| | | "version": "4.0.5", |
| | |
| | | "dev": true, |
| | | "license": "Python-2.0" |
| | | }, |
| | | "node_modules/aria-query": { |
| | | "version": "5.3.0", |
| | | "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", |
| | | "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", |
| | | "dev": true, |
| | | "license": "Apache-2.0", |
| | | "dependencies": { |
| | | "dequal": "^2.0.3" |
| | | } |
| | | }, |
| | | "node_modules/array-buffer-byte-length": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", |
| | | "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.3", |
| | | "is-array-buffer": "^3.0.5" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/assertion-error": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", |
| | |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=12" |
| | | } |
| | | }, |
| | | "node_modules/asynckit": { |
| | | "version": "0.4.0", |
| | | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", |
| | | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/available-typed-arrays": { |
| | | "version": "1.0.7", |
| | | "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", |
| | | "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "possible-typed-array-names": "^1.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/axios": { |
| | | "version": "1.13.2", |
| | | "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", |
| | | "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "follow-redirects": "^1.15.6", |
| | | "form-data": "^4.0.4", |
| | | "proxy-from-env": "^1.1.0" |
| | | } |
| | | }, |
| | | "node_modules/balanced-match": { |
| | |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/call-bind": { |
| | | "version": "1.0.8", |
| | | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", |
| | | "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bind-apply-helpers": "^1.0.0", |
| | | "es-define-property": "^1.0.0", |
| | | "get-intrinsic": "^1.2.4", |
| | | "set-function-length": "^1.2.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/call-bind-apply-helpers": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", |
| | | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "es-errors": "^1.3.0", |
| | | "function-bind": "^1.1.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/call-bound": { |
| | | "version": "1.0.4", |
| | | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", |
| | | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bind-apply-helpers": "^1.0.2", |
| | | "get-intrinsic": "^1.3.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/callsites": { |
| | | "version": "3.1.0", |
| | | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", |
| | |
| | | "node": ">= 16" |
| | | } |
| | | }, |
| | | "node_modules/cli-width": { |
| | | "version": "4.1.0", |
| | | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", |
| | | "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", |
| | | "dev": true, |
| | | "license": "ISC", |
| | | "engines": { |
| | | "node": ">= 12" |
| | | } |
| | | }, |
| | | "node_modules/cliui": { |
| | | "version": "8.0.1", |
| | | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", |
| | | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", |
| | | "dev": true, |
| | | "license": "ISC", |
| | | "dependencies": { |
| | | "string-width": "^4.2.0", |
| | | "strip-ansi": "^6.0.1", |
| | | "wrap-ansi": "^7.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=12" |
| | | } |
| | | }, |
| | | "node_modules/cliui/node_modules/ansi-regex": { |
| | | "version": "5.0.1", |
| | | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", |
| | | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/cliui/node_modules/emoji-regex": { |
| | | "version": "8.0.0", |
| | | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", |
| | | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/cliui/node_modules/string-width": { |
| | | "version": "4.2.3", |
| | | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", |
| | | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "emoji-regex": "^8.0.0", |
| | | "is-fullwidth-code-point": "^3.0.0", |
| | | "strip-ansi": "^6.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/cliui/node_modules/strip-ansi": { |
| | | "version": "6.0.1", |
| | | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", |
| | | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "ansi-regex": "^5.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/cliui/node_modules/wrap-ansi": { |
| | | "version": "7.0.0", |
| | | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", |
| | | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "ansi-styles": "^4.0.0", |
| | | "string-width": "^4.1.0", |
| | | "strip-ansi": "^6.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=10" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" |
| | | } |
| | | }, |
| | | "node_modules/color-convert": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", |
| | |
| | | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/combined-stream": { |
| | | "version": "1.0.8", |
| | | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", |
| | | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "delayed-stream": "~1.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.8" |
| | | } |
| | | }, |
| | | "node_modules/commander": { |
| | | "version": "10.0.1", |
| | |
| | | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/cookie": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", |
| | | "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/copy-anything": { |
| | | "version": "4.0.5", |
| | |
| | | "engines": { |
| | | "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" |
| | | } |
| | | }, |
| | | "node_modules/css.escape": { |
| | | "version": "1.5.1", |
| | | "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", |
| | | "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/cssesc": { |
| | | "version": "3.0.0", |
| | |
| | | "node": ">=6" |
| | | } |
| | | }, |
| | | "node_modules/deep-equal": { |
| | | "version": "2.2.3", |
| | | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", |
| | | "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "array-buffer-byte-length": "^1.0.0", |
| | | "call-bind": "^1.0.5", |
| | | "es-get-iterator": "^1.1.3", |
| | | "get-intrinsic": "^1.2.2", |
| | | "is-arguments": "^1.1.1", |
| | | "is-array-buffer": "^3.0.2", |
| | | "is-date-object": "^1.0.5", |
| | | "is-regex": "^1.1.4", |
| | | "is-shared-array-buffer": "^1.0.2", |
| | | "isarray": "^2.0.5", |
| | | "object-is": "^1.1.5", |
| | | "object-keys": "^1.1.1", |
| | | "object.assign": "^4.1.4", |
| | | "regexp.prototype.flags": "^1.5.1", |
| | | "side-channel": "^1.0.4", |
| | | "which-boxed-primitive": "^1.0.2", |
| | | "which-collection": "^1.0.1", |
| | | "which-typed-array": "^1.1.13" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/deep-is": { |
| | | "version": "0.1.4", |
| | | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", |
| | |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/define-data-property": { |
| | | "version": "1.1.4", |
| | | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", |
| | | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "es-define-property": "^1.0.0", |
| | | "es-errors": "^1.3.0", |
| | | "gopd": "^1.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/define-lazy-prop": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", |
| | |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/define-properties": { |
| | | "version": "1.2.1", |
| | | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", |
| | | "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "define-data-property": "^1.0.1", |
| | | "has-property-descriptors": "^1.0.0", |
| | | "object-keys": "^1.1.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/delayed-stream": { |
| | | "version": "1.0.0", |
| | | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", |
| | | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=0.4.0" |
| | | } |
| | | }, |
| | | "node_modules/dequal": { |
| | | "version": "2.0.3", |
| | | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", |
| | | "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=6" |
| | | } |
| | | }, |
| | | "node_modules/dom-accessibility-api": { |
| | | "version": "0.5.16", |
| | | "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", |
| | | "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/dunder-proto": { |
| | | "version": "1.0.1", |
| | | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", |
| | | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bind-apply-helpers": "^1.0.1", |
| | | "es-errors": "^1.3.0", |
| | | "gopd": "^1.2.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/eastasianwidth": { |
| | |
| | | "url": "https://github.com/sponsors/antfu" |
| | | } |
| | | }, |
| | | "node_modules/es-define-property": { |
| | | "version": "1.0.1", |
| | | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", |
| | | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/es-errors": { |
| | | "version": "1.3.0", |
| | | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", |
| | | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/es-get-iterator": { |
| | | "version": "1.1.3", |
| | | "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", |
| | | "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bind": "^1.0.2", |
| | | "get-intrinsic": "^1.1.3", |
| | | "has-symbols": "^1.0.3", |
| | | "is-arguments": "^1.1.1", |
| | | "is-map": "^2.0.2", |
| | | "is-set": "^2.0.2", |
| | | "is-string": "^1.0.7", |
| | | "isarray": "^2.0.5", |
| | | "stop-iteration-iterator": "^1.0.0" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/es-module-lexer": { |
| | | "version": "1.7.0", |
| | | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", |
| | | "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/es-object-atoms": { |
| | | "version": "1.1.1", |
| | | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", |
| | | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "es-errors": "^1.3.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/es-set-tostringtag": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", |
| | | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "es-errors": "^1.3.0", |
| | | "get-intrinsic": "^1.2.6", |
| | | "has-tostringtag": "^1.0.2", |
| | | "hasown": "^2.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/esbuild": { |
| | | "version": "0.25.12", |
| | |
| | | "dev": true, |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/follow-redirects": { |
| | | "version": "1.15.11", |
| | | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", |
| | | "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", |
| | | "funding": [ |
| | | { |
| | | "type": "individual", |
| | | "url": "https://github.com/sponsors/RubenVerborgh" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=4.0" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "debug": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/for-each": { |
| | | "version": "0.3.5", |
| | | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", |
| | | "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "is-callable": "^1.2.7" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/foreground-child": { |
| | | "version": "3.3.1", |
| | | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", |
| | |
| | | "url": "https://github.com/sponsors/isaacs" |
| | | } |
| | | }, |
| | | "node_modules/form-data": { |
| | | "version": "4.0.4", |
| | | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", |
| | | "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "asynckit": "^0.4.0", |
| | | "combined-stream": "^1.0.8", |
| | | "es-set-tostringtag": "^2.1.0", |
| | | "hasown": "^2.0.2", |
| | | "mime-types": "^2.1.12" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 6" |
| | | } |
| | | }, |
| | | "node_modules/fsevents": { |
| | | "version": "2.3.2", |
| | | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", |
| | |
| | | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" |
| | | } |
| | | }, |
| | | "node_modules/function-bind": { |
| | | "version": "1.1.2", |
| | | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", |
| | | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/functions-have-names": { |
| | | "version": "1.2.3", |
| | | "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", |
| | | "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/gensync": { |
| | | "version": "1.0.0-beta.2", |
| | | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", |
| | |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=6.9.0" |
| | | } |
| | | }, |
| | | "node_modules/get-caller-file": { |
| | | "version": "2.0.5", |
| | | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", |
| | | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", |
| | | "dev": true, |
| | | "license": "ISC", |
| | | "engines": { |
| | | "node": "6.* || 8.* || >= 10.*" |
| | | } |
| | | }, |
| | | "node_modules/get-intrinsic": { |
| | | "version": "1.3.0", |
| | | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", |
| | | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bind-apply-helpers": "^1.0.2", |
| | | "es-define-property": "^1.0.1", |
| | | "es-errors": "^1.3.0", |
| | | "es-object-atoms": "^1.1.1", |
| | | "function-bind": "^1.1.2", |
| | | "get-proto": "^1.0.1", |
| | | "gopd": "^1.2.0", |
| | | "has-symbols": "^1.1.0", |
| | | "hasown": "^2.0.2", |
| | | "math-intrinsics": "^1.1.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/get-proto": { |
| | | "version": "1.0.1", |
| | | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", |
| | | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "dunder-proto": "^1.0.1", |
| | | "es-object-atoms": "^1.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/glob": { |
| | |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/gopd": { |
| | | "version": "1.2.0", |
| | | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", |
| | | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/graceful-fs": { |
| | | "version": "4.2.11", |
| | | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", |
| | |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/graphql": { |
| | | "version": "16.12.0", |
| | | "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", |
| | | "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" |
| | | } |
| | | }, |
| | | "node_modules/has-bigints": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", |
| | | "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/has-flag": { |
| | | "version": "4.0.0", |
| | | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", |
| | |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/has-property-descriptors": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", |
| | | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "es-define-property": "^1.0.0" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/has-symbols": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", |
| | | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/has-tostringtag": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", |
| | | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "has-symbols": "^1.0.3" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/hasown": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", |
| | | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "function-bind": "^1.1.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/headers-polyfill": { |
| | | "version": "4.0.3", |
| | | "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", |
| | | "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/hookable": { |
| | | "version": "5.5.3", |
| | |
| | | "node": ">=0.8.19" |
| | | } |
| | | }, |
| | | "node_modules/indent-string": { |
| | | "version": "4.0.0", |
| | | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", |
| | | "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/ini": { |
| | | "version": "1.3.8", |
| | | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", |
| | | "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", |
| | | "dev": true, |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/internal-slot": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", |
| | | "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "es-errors": "^1.3.0", |
| | | "hasown": "^2.0.2", |
| | | "side-channel": "^1.1.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/is-arguments": { |
| | | "version": "1.2.0", |
| | | "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", |
| | | "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.2", |
| | | "has-tostringtag": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-array-buffer": { |
| | | "version": "3.0.5", |
| | | "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", |
| | | "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bind": "^1.0.8", |
| | | "call-bound": "^1.0.3", |
| | | "get-intrinsic": "^1.2.6" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-bigint": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", |
| | | "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "has-bigints": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-boolean-object": { |
| | | "version": "1.2.2", |
| | | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", |
| | | "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.3", |
| | | "has-tostringtag": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-callable": { |
| | | "version": "1.2.7", |
| | | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", |
| | | "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-date-object": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", |
| | | "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.2", |
| | | "has-tostringtag": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-docker": { |
| | | "version": "3.0.0", |
| | |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/is-map": { |
| | | "version": "2.0.3", |
| | | "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", |
| | | "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-node-process": { |
| | | "version": "1.2.0", |
| | | "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", |
| | | "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/is-number": { |
| | | "version": "7.0.0", |
| | | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", |
| | |
| | | "node": ">=0.12.0" |
| | | } |
| | | }, |
| | | "node_modules/is-number-object": { |
| | | "version": "1.1.1", |
| | | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", |
| | | "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.3", |
| | | "has-tostringtag": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-potential-custom-element-name": { |
| | | "version": "1.0.1", |
| | | "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", |
| | | "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/is-regex": { |
| | | "version": "1.2.1", |
| | | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", |
| | | "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.2", |
| | | "gopd": "^1.2.0", |
| | | "has-tostringtag": "^1.0.2", |
| | | "hasown": "^2.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-set": { |
| | | "version": "2.0.3", |
| | | "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", |
| | | "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-shared-array-buffer": { |
| | | "version": "1.0.4", |
| | | "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", |
| | | "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.3" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-string": { |
| | | "version": "1.1.1", |
| | | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", |
| | | "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.3", |
| | | "has-tostringtag": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-symbol": { |
| | | "version": "1.1.1", |
| | | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", |
| | | "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.2", |
| | | "has-symbols": "^1.1.0", |
| | | "safe-regex-test": "^1.1.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-weakmap": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", |
| | | "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-weakset": { |
| | | "version": "2.0.4", |
| | | "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", |
| | | "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.3", |
| | | "get-intrinsic": "^1.2.6" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-what": { |
| | | "version": "5.5.0", |
| | |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/isarray": { |
| | | "version": "2.0.5", |
| | | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", |
| | | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/isexe": { |
| | | "version": "2.0.0", |
| | |
| | | "yallist": "^3.0.2" |
| | | } |
| | | }, |
| | | "node_modules/lz-string": { |
| | | "version": "1.5.0", |
| | | "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", |
| | | "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "bin": { |
| | | "lz-string": "bin/bin.js" |
| | | } |
| | | }, |
| | | "node_modules/magic-string": { |
| | | "version": "0.30.21", |
| | | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", |
| | |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@jridgewell/sourcemap-codec": "^1.5.5" |
| | | } |
| | | }, |
| | | "node_modules/math-intrinsics": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", |
| | | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/mdn-data": { |
| | |
| | | }, |
| | | "engines": { |
| | | "node": ">=8.6" |
| | | } |
| | | }, |
| | | "node_modules/mime-db": { |
| | | "version": "1.52.0", |
| | | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", |
| | | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.6" |
| | | } |
| | | }, |
| | | "node_modules/mime-types": { |
| | | "version": "2.1.35", |
| | | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", |
| | | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "mime-db": "1.52.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.6" |
| | | } |
| | | }, |
| | | "node_modules/min-indent": { |
| | | "version": "1.0.1", |
| | | "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", |
| | | "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=4" |
| | | } |
| | | }, |
| | | "node_modules/minimatch": { |
| | |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/msw": { |
| | | "version": "2.12.1", |
| | | "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.1.tgz", |
| | | "integrity": "sha512-arzsi9IZjjByiEw21gSUP82qHM8zkV69nNpWV6W4z72KiLvsDWoOp678ORV6cNfU/JGhlX0SsnD4oXo9gI6I2A==", |
| | | "dev": true, |
| | | "hasInstallScript": true, |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@inquirer/confirm": "^5.0.0", |
| | | "@mswjs/interceptors": "^0.40.0", |
| | | "@open-draft/deferred-promise": "^2.2.0", |
| | | "@types/statuses": "^2.0.4", |
| | | "cookie": "^1.0.2", |
| | | "graphql": "^16.8.1", |
| | | "headers-polyfill": "^4.0.2", |
| | | "is-node-process": "^1.2.0", |
| | | "outvariant": "^1.4.3", |
| | | "path-to-regexp": "^6.3.0", |
| | | "picocolors": "^1.1.1", |
| | | "rettime": "^0.7.0", |
| | | "statuses": "^2.0.2", |
| | | "strict-event-emitter": "^0.5.1", |
| | | "tough-cookie": "^6.0.0", |
| | | "type-fest": "^4.26.1", |
| | | "until-async": "^3.0.2", |
| | | "yargs": "^17.7.2" |
| | | }, |
| | | "bin": { |
| | | "msw": "cli/index.js" |
| | | }, |
| | | "engines": { |
| | | "node": ">=18" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/mswjs" |
| | | }, |
| | | "peerDependencies": { |
| | | "typescript": ">= 4.8.x" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "typescript": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/muggle-string": { |
| | | "version": "0.4.1", |
| | | "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", |
| | | "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/mute-stream": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", |
| | | "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", |
| | | "dev": true, |
| | | "license": "ISC", |
| | | "engines": { |
| | | "node": "^20.17.0 || >=22.9.0" |
| | | } |
| | | }, |
| | | "node_modules/nanoid": { |
| | | "version": "3.3.11", |
| | |
| | | "url": "https://github.com/fb55/nth-check?sponsor=1" |
| | | } |
| | | }, |
| | | "node_modules/object-inspect": { |
| | | "version": "1.13.4", |
| | | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", |
| | | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/object-is": { |
| | | "version": "1.1.6", |
| | | "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", |
| | | "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bind": "^1.0.7", |
| | | "define-properties": "^1.2.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/object-keys": { |
| | | "version": "1.1.1", |
| | | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", |
| | | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/object.assign": { |
| | | "version": "4.1.7", |
| | | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", |
| | | "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bind": "^1.0.8", |
| | | "call-bound": "^1.0.3", |
| | | "define-properties": "^1.2.1", |
| | | "es-object-atoms": "^1.0.0", |
| | | "has-symbols": "^1.1.0", |
| | | "object-keys": "^1.1.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/ohash": { |
| | | "version": "2.0.11", |
| | | "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", |
| | |
| | | "engines": { |
| | | "node": ">= 0.8.0" |
| | | } |
| | | }, |
| | | "node_modules/outvariant": { |
| | | "version": "1.4.3", |
| | | "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", |
| | | "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/p-limit": { |
| | | "version": "3.1.0", |
| | |
| | | "dev": true, |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/path-to-regexp": { |
| | | "version": "6.3.0", |
| | | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", |
| | | "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/pathe": { |
| | | "version": "2.0.3", |
| | | "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", |
| | |
| | | "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", |
| | | "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@vue/devtools-api": "^7.7.7" |
| | | }, |
| | |
| | | }, |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/possible-typed-array-names": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", |
| | | "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/postcss": { |
| | |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/pretty-format": { |
| | | "version": "27.5.1", |
| | | "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", |
| | | "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "ansi-regex": "^5.0.1", |
| | | "ansi-styles": "^5.0.0", |
| | | "react-is": "^17.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" |
| | | } |
| | | }, |
| | | "node_modules/pretty-format/node_modules/ansi-regex": { |
| | | "version": "5.0.1", |
| | | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", |
| | | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/pretty-format/node_modules/ansi-styles": { |
| | | "version": "5.2.0", |
| | | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", |
| | | "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=10" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/chalk/ansi-styles?sponsor=1" |
| | | } |
| | | }, |
| | | "node_modules/proto-list": { |
| | | "version": "1.2.4", |
| | | "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", |
| | | "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", |
| | | "dev": true, |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/proxy-from-env": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", |
| | | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/punycode": { |
| | | "version": "2.3.1", |
| | |
| | | ], |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/react-is": { |
| | | "version": "17.0.2", |
| | | "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", |
| | | "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/read-package-json-fast": { |
| | | "version": "4.0.0", |
| | | "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", |
| | |
| | | }, |
| | | "engines": { |
| | | "node": "^18.17.0 || >=20.5.0" |
| | | } |
| | | }, |
| | | "node_modules/redent": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", |
| | | "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "indent-string": "^4.0.0", |
| | | "strip-indent": "^3.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/regexp.prototype.flags": { |
| | | "version": "1.5.4", |
| | | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", |
| | | "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bind": "^1.0.8", |
| | | "define-properties": "^1.2.1", |
| | | "es-errors": "^1.3.0", |
| | | "get-proto": "^1.0.1", |
| | | "gopd": "^1.2.0", |
| | | "set-function-name": "^2.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/require-directory": { |
| | | "version": "2.1.1", |
| | | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", |
| | | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/require-from-string": { |
| | |
| | | "engines": { |
| | | "node": ">=4" |
| | | } |
| | | }, |
| | | "node_modules/rettime": { |
| | | "version": "0.7.0", |
| | | "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", |
| | | "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/reusify": { |
| | | "version": "1.1.0", |
| | |
| | | "queue-microtask": "^1.2.2" |
| | | } |
| | | }, |
| | | "node_modules/safe-regex-test": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", |
| | | "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.2", |
| | | "es-errors": "^1.3.0", |
| | | "is-regex": "^1.2.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/safer-buffer": { |
| | | "version": "2.1.2", |
| | | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", |
| | |
| | | "license": "ISC", |
| | | "bin": { |
| | | "semver": "bin/semver.js" |
| | | } |
| | | }, |
| | | "node_modules/set-function-length": { |
| | | "version": "1.2.2", |
| | | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", |
| | | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "define-data-property": "^1.1.4", |
| | | "es-errors": "^1.3.0", |
| | | "function-bind": "^1.1.2", |
| | | "get-intrinsic": "^1.2.4", |
| | | "gopd": "^1.0.1", |
| | | "has-property-descriptors": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/set-function-name": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", |
| | | "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "define-data-property": "^1.1.4", |
| | | "es-errors": "^1.3.0", |
| | | "functions-have-names": "^1.2.3", |
| | | "has-property-descriptors": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/shebang-command": { |
| | |
| | | "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/side-channel": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", |
| | | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "es-errors": "^1.3.0", |
| | | "object-inspect": "^1.13.3", |
| | | "side-channel-list": "^1.0.0", |
| | | "side-channel-map": "^1.0.1", |
| | | "side-channel-weakmap": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/side-channel-list": { |
| | | "version": "1.0.0", |
| | | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", |
| | | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "es-errors": "^1.3.0", |
| | | "object-inspect": "^1.13.3" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/side-channel-map": { |
| | | "version": "1.0.1", |
| | | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", |
| | | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.2", |
| | | "es-errors": "^1.3.0", |
| | | "get-intrinsic": "^1.2.5", |
| | | "object-inspect": "^1.13.3" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/side-channel-weakmap": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", |
| | | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "call-bound": "^1.0.2", |
| | | "es-errors": "^1.3.0", |
| | | "get-intrinsic": "^1.2.5", |
| | | "object-inspect": "^1.13.3", |
| | | "side-channel-map": "^1.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/statuses": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", |
| | | "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.8" |
| | | } |
| | | }, |
| | | "node_modules/std-env": { |
| | | "version": "3.10.0", |
| | | "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", |
| | | "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/stop-iteration-iterator": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", |
| | | "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "es-errors": "^1.3.0", |
| | | "internal-slot": "^1.1.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/strict-event-emitter": { |
| | | "version": "0.5.1", |
| | | "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", |
| | | "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | |
| | | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/strip-indent": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", |
| | | "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "min-indent": "^1.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | |
| | | "node": ">= 0.8.0" |
| | | } |
| | | }, |
| | | "node_modules/type-fest": { |
| | | "version": "4.41.0", |
| | | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", |
| | | "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", |
| | | "dev": true, |
| | | "license": "(MIT OR CC0-1.0)", |
| | | "engines": { |
| | | "node": ">=16" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/typescript": { |
| | | "version": "5.9.3", |
| | | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", |
| | |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/jonschlinkert" |
| | | } |
| | | }, |
| | | "node_modules/until-async": { |
| | | "version": "3.0.2", |
| | | "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", |
| | | "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/kettanaito" |
| | | } |
| | | }, |
| | | "node_modules/update-browserslist-db": { |
| | |
| | | "node": ">= 8" |
| | | } |
| | | }, |
| | | "node_modules/which-boxed-primitive": { |
| | | "version": "1.1.1", |
| | | "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", |
| | | "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "is-bigint": "^1.1.0", |
| | | "is-boolean-object": "^1.2.1", |
| | | "is-number-object": "^1.1.1", |
| | | "is-string": "^1.1.1", |
| | | "is-symbol": "^1.1.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/which-collection": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", |
| | | "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "is-map": "^2.0.3", |
| | | "is-set": "^2.0.3", |
| | | "is-weakmap": "^2.0.2", |
| | | "is-weakset": "^2.0.3" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/which-typed-array": { |
| | | "version": "1.1.19", |
| | | "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", |
| | | "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "available-typed-arrays": "^1.0.7", |
| | | "call-bind": "^1.0.8", |
| | | "call-bound": "^1.0.4", |
| | | "for-each": "^0.3.5", |
| | | "get-proto": "^1.0.1", |
| | | "gopd": "^1.2.0", |
| | | "has-tostringtag": "^1.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/why-is-node-running": { |
| | | "version": "2.3.0", |
| | | "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", |
| | |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/y18n": { |
| | | "version": "5.0.8", |
| | | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", |
| | | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", |
| | | "dev": true, |
| | | "license": "ISC", |
| | | "engines": { |
| | | "node": ">=10" |
| | | } |
| | | }, |
| | | "node_modules/yallist": { |
| | | "version": "3.1.1", |
| | | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", |
| | | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", |
| | | "dev": true, |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/yargs": { |
| | | "version": "17.7.2", |
| | | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", |
| | | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "cliui": "^8.0.1", |
| | | "escalade": "^3.1.1", |
| | | "get-caller-file": "^2.0.5", |
| | | "require-directory": "^2.1.1", |
| | | "string-width": "^4.2.3", |
| | | "y18n": "^5.0.5", |
| | | "yargs-parser": "^21.1.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=12" |
| | | } |
| | | }, |
| | | "node_modules/yargs-parser": { |
| | | "version": "21.1.1", |
| | | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", |
| | | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", |
| | | "dev": true, |
| | | "license": "ISC", |
| | | "engines": { |
| | | "node": ">=12" |
| | | } |
| | | }, |
| | | "node_modules/yargs/node_modules/ansi-regex": { |
| | | "version": "5.0.1", |
| | | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", |
| | | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/yargs/node_modules/emoji-regex": { |
| | | "version": "8.0.0", |
| | | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", |
| | | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/yargs/node_modules/string-width": { |
| | | "version": "4.2.3", |
| | | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", |
| | | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "emoji-regex": "^8.0.0", |
| | | "is-fullwidth-code-point": "^3.0.0", |
| | | "strip-ansi": "^6.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/yargs/node_modules/strip-ansi": { |
| | | "version": "6.0.1", |
| | | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", |
| | | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "ansi-regex": "^5.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/yocto-queue": { |
| | | "version": "0.1.0", |
| | |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/yoctocolors-cjs": { |
| | | "version": "2.1.3", |
| | | "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", |
| | | "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=18" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | "vue-router": "^4.6.3" |
| | | }, |
| | | "devDependencies": { |
| | | "@pinia/testing": "^1.0.3", |
| | | "@playwright/test": "^1.56.1", |
| | | "@testing-library/jest-dom": "^6.9.1", |
| | | "@testing-library/user-event": "^14.6.1", |
| | | "@testing-library/vue": "^8.1.0", |
| | | "@tsconfig/node22": "^22.0.2", |
| | | "@types/jsdom": "^27.0.0", |
| | | "@types/node": "^22.18.11", |
| | |
| | | "eslint-plugin-vue": "~10.5.0", |
| | | "jiti": "^2.6.1", |
| | | "jsdom": "^27.0.1", |
| | | "msw": "^2.12.1", |
| | | "npm-run-all2": "^8.0.4", |
| | | "prettier": "^3.6.2", |
| | | "prettier-plugin-tailwindcss": "^0.7.1", |
| | |
| | | "vite-plugin-vue-devtools": "^8.0.3", |
| | | "vitest": "^3.2.4", |
| | | "vue-tsc": "^3.1.1" |
| | | }, |
| | | "msw": { |
| | | "workerDirectory": [ |
| | | "public" |
| | | ] |
| | | } |
| | | } |
| 比對新檔案 |
| | |
| | | /* eslint-disable */ |
| | | /* tslint:disable */ |
| | | |
| | | /** |
| | | * Mock Service Worker. |
| | | * @see https://github.com/mswjs/msw |
| | | * - Please do NOT modify this file. |
| | | */ |
| | | |
| | | const PACKAGE_VERSION = '2.12.1' |
| | | const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' |
| | | const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') |
| | | const activeClientIds = new Set() |
| | | |
| | | addEventListener('install', function () { |
| | | self.skipWaiting() |
| | | }) |
| | | |
| | | addEventListener('activate', function (event) { |
| | | event.waitUntil(self.clients.claim()) |
| | | }) |
| | | |
| | | addEventListener('message', async function (event) { |
| | | const clientId = Reflect.get(event.source || {}, 'id') |
| | | |
| | | if (!clientId || !self.clients) { |
| | | return |
| | | } |
| | | |
| | | const client = await self.clients.get(clientId) |
| | | |
| | | if (!client) { |
| | | return |
| | | } |
| | | |
| | | const allClients = await self.clients.matchAll({ |
| | | type: 'window', |
| | | }) |
| | | |
| | | switch (event.data) { |
| | | case 'KEEPALIVE_REQUEST': { |
| | | sendToClient(client, { |
| | | type: 'KEEPALIVE_RESPONSE', |
| | | }) |
| | | break |
| | | } |
| | | |
| | | case 'INTEGRITY_CHECK_REQUEST': { |
| | | sendToClient(client, { |
| | | type: 'INTEGRITY_CHECK_RESPONSE', |
| | | payload: { |
| | | packageVersion: PACKAGE_VERSION, |
| | | checksum: INTEGRITY_CHECKSUM, |
| | | }, |
| | | }) |
| | | break |
| | | } |
| | | |
| | | case 'MOCK_ACTIVATE': { |
| | | activeClientIds.add(clientId) |
| | | |
| | | sendToClient(client, { |
| | | type: 'MOCKING_ENABLED', |
| | | payload: { |
| | | client: { |
| | | id: client.id, |
| | | frameType: client.frameType, |
| | | }, |
| | | }, |
| | | }) |
| | | break |
| | | } |
| | | |
| | | case 'CLIENT_CLOSED': { |
| | | activeClientIds.delete(clientId) |
| | | |
| | | const remainingClients = allClients.filter((client) => { |
| | | return client.id !== clientId |
| | | }) |
| | | |
| | | // Unregister itself when there are no more clients |
| | | if (remainingClients.length === 0) { |
| | | self.registration.unregister() |
| | | } |
| | | |
| | | break |
| | | } |
| | | } |
| | | }) |
| | | |
| | | addEventListener('fetch', function (event) { |
| | | const requestInterceptedAt = Date.now() |
| | | |
| | | // Bypass navigation requests. |
| | | if (event.request.mode === 'navigate') { |
| | | return |
| | | } |
| | | |
| | | // Opening the DevTools triggers the "only-if-cached" request |
| | | // that cannot be handled by the worker. Bypass such requests. |
| | | if ( |
| | | event.request.cache === 'only-if-cached' && |
| | | event.request.mode !== 'same-origin' |
| | | ) { |
| | | return |
| | | } |
| | | |
| | | // Bypass all requests when there are no active clients. |
| | | // Prevents the self-unregistered worked from handling requests |
| | | // after it's been terminated (still remains active until the next reload). |
| | | if (activeClientIds.size === 0) { |
| | | return |
| | | } |
| | | |
| | | const requestId = crypto.randomUUID() |
| | | event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) |
| | | }) |
| | | |
| | | /** |
| | | * @param {FetchEvent} event |
| | | * @param {string} requestId |
| | | * @param {number} requestInterceptedAt |
| | | */ |
| | | async function handleRequest(event, requestId, requestInterceptedAt) { |
| | | const client = await resolveMainClient(event) |
| | | const requestCloneForEvents = event.request.clone() |
| | | const response = await getResponse( |
| | | event, |
| | | client, |
| | | requestId, |
| | | requestInterceptedAt, |
| | | ) |
| | | |
| | | // Send back the response clone for the "response:*" life-cycle events. |
| | | // Ensure MSW is active and ready to handle the message, otherwise |
| | | // this message will pend indefinitely. |
| | | if (client && activeClientIds.has(client.id)) { |
| | | const serializedRequest = await serializeRequest(requestCloneForEvents) |
| | | |
| | | // Clone the response so both the client and the library could consume it. |
| | | const responseClone = response.clone() |
| | | |
| | | sendToClient( |
| | | client, |
| | | { |
| | | type: 'RESPONSE', |
| | | payload: { |
| | | isMockedResponse: IS_MOCKED_RESPONSE in response, |
| | | request: { |
| | | id: requestId, |
| | | ...serializedRequest, |
| | | }, |
| | | response: { |
| | | type: responseClone.type, |
| | | status: responseClone.status, |
| | | statusText: responseClone.statusText, |
| | | headers: Object.fromEntries(responseClone.headers.entries()), |
| | | body: responseClone.body, |
| | | }, |
| | | }, |
| | | }, |
| | | responseClone.body ? [serializedRequest.body, responseClone.body] : [], |
| | | ) |
| | | } |
| | | |
| | | return response |
| | | } |
| | | |
| | | /** |
| | | * Resolve the main client for the given event. |
| | | * Client that issues a request doesn't necessarily equal the client |
| | | * that registered the worker. It's with the latter the worker should |
| | | * communicate with during the response resolving phase. |
| | | * @param {FetchEvent} event |
| | | * @returns {Promise<Client | undefined>} |
| | | */ |
| | | async function resolveMainClient(event) { |
| | | const client = await self.clients.get(event.clientId) |
| | | |
| | | if (activeClientIds.has(event.clientId)) { |
| | | return client |
| | | } |
| | | |
| | | if (client?.frameType === 'top-level') { |
| | | return client |
| | | } |
| | | |
| | | const allClients = await self.clients.matchAll({ |
| | | type: 'window', |
| | | }) |
| | | |
| | | return allClients |
| | | .filter((client) => { |
| | | // Get only those clients that are currently visible. |
| | | return client.visibilityState === 'visible' |
| | | }) |
| | | .find((client) => { |
| | | // Find the client ID that's recorded in the |
| | | // set of clients that have registered the worker. |
| | | return activeClientIds.has(client.id) |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * @param {FetchEvent} event |
| | | * @param {Client | undefined} client |
| | | * @param {string} requestId |
| | | * @param {number} requestInterceptedAt |
| | | * @returns {Promise<Response>} |
| | | */ |
| | | async function getResponse(event, client, requestId, requestInterceptedAt) { |
| | | // Clone the request because it might've been already used |
| | | // (i.e. its body has been read and sent to the client). |
| | | const requestClone = event.request.clone() |
| | | |
| | | function passthrough() { |
| | | // Cast the request headers to a new Headers instance |
| | | // so the headers can be manipulated with. |
| | | const headers = new Headers(requestClone.headers) |
| | | |
| | | // Remove the "accept" header value that marked this request as passthrough. |
| | | // This prevents request alteration and also keeps it compliant with the |
| | | // user-defined CORS policies. |
| | | const acceptHeader = headers.get('accept') |
| | | if (acceptHeader) { |
| | | const values = acceptHeader.split(',').map((value) => value.trim()) |
| | | const filteredValues = values.filter( |
| | | (value) => value !== 'msw/passthrough', |
| | | ) |
| | | |
| | | if (filteredValues.length > 0) { |
| | | headers.set('accept', filteredValues.join(', ')) |
| | | } else { |
| | | headers.delete('accept') |
| | | } |
| | | } |
| | | |
| | | return fetch(requestClone, { headers }) |
| | | } |
| | | |
| | | // Bypass mocking when the client is not active. |
| | | if (!client) { |
| | | return passthrough() |
| | | } |
| | | |
| | | // Bypass initial page load requests (i.e. static assets). |
| | | // The absence of the immediate/parent client in the map of the active clients |
| | | // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet |
| | | // and is not ready to handle requests. |
| | | if (!activeClientIds.has(client.id)) { |
| | | return passthrough() |
| | | } |
| | | |
| | | // Notify the client that a request has been intercepted. |
| | | const serializedRequest = await serializeRequest(event.request) |
| | | const clientMessage = await sendToClient( |
| | | client, |
| | | { |
| | | type: 'REQUEST', |
| | | payload: { |
| | | id: requestId, |
| | | interceptedAt: requestInterceptedAt, |
| | | ...serializedRequest, |
| | | }, |
| | | }, |
| | | [serializedRequest.body], |
| | | ) |
| | | |
| | | switch (clientMessage.type) { |
| | | case 'MOCK_RESPONSE': { |
| | | return respondWithMock(clientMessage.data) |
| | | } |
| | | |
| | | case 'PASSTHROUGH': { |
| | | return passthrough() |
| | | } |
| | | } |
| | | |
| | | return passthrough() |
| | | } |
| | | |
| | | /** |
| | | * @param {Client} client |
| | | * @param {any} message |
| | | * @param {Array<Transferable>} transferrables |
| | | * @returns {Promise<any>} |
| | | */ |
| | | function sendToClient(client, message, transferrables = []) { |
| | | return new Promise((resolve, reject) => { |
| | | const channel = new MessageChannel() |
| | | |
| | | channel.port1.onmessage = (event) => { |
| | | if (event.data && event.data.error) { |
| | | return reject(event.data.error) |
| | | } |
| | | |
| | | resolve(event.data) |
| | | } |
| | | |
| | | client.postMessage(message, [ |
| | | channel.port2, |
| | | ...transferrables.filter(Boolean), |
| | | ]) |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * @param {Response} response |
| | | * @returns {Response} |
| | | */ |
| | | function respondWithMock(response) { |
| | | // Setting response status code to 0 is a no-op. |
| | | // However, when responding with a "Response.error()", the produced Response |
| | | // instance will have status code set to 0. Since it's not possible to create |
| | | // a Response instance with status code 0, handle that use-case separately. |
| | | if (response.status === 0) { |
| | | return Response.error() |
| | | } |
| | | |
| | | const mockedResponse = new Response(response.body, response) |
| | | |
| | | Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { |
| | | value: true, |
| | | enumerable: true, |
| | | }) |
| | | |
| | | return mockedResponse |
| | | } |
| | | |
| | | /** |
| | | * @param {Request} request |
| | | */ |
| | | async function serializeRequest(request) { |
| | | return { |
| | | url: request.url, |
| | | mode: request.mode, |
| | | method: request.method, |
| | | headers: Object.fromEntries(request.headers.entries()), |
| | | cache: request.cache, |
| | | credentials: request.credentials, |
| | | destination: request.destination, |
| | | integrity: request.integrity, |
| | | redirect: request.redirect, |
| | | referrer: request.referrer, |
| | | referrerPolicy: request.referrerPolicy, |
| | | body: await request.arrayBuffer(), |
| | | keepalive: request.keepalive, |
| | | } |
| | | } |
| 比對新檔案 |
| | |
| | | import { beforeAll, afterAll, beforeEach, afterEach } from 'vitest' |
| | | import { cleanup } from '@testing-library/vue' |
| | | import { server } from './src/mocks/node' |
| | | import '@testing-library/jest-dom/vitest' |
| | | // Start the server before all tests |
| | | beforeAll(() => { |
| | | server.listen({ onUnhandledRequest: 'error' }) |
| | | }) |
| | | |
| | | beforeEach(() => { |
| | | server.resetHandlers() |
| | | }) |
| | | |
| | | // Clean up after the tests are finished |
| | | afterAll(() => { |
| | | server.close() |
| | | }) |
| | | |
| | | afterEach(() => { |
| | | cleanup() |
| | | }) |
| | |
| | | Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the |
| | | documentation |
| | | </p> |
| | | <hr /> |
| | | <router-view></router-view> |
| | | </template> |
| | | |
| | | <style scoped></style> |
| | |
| | | import { describe, it, expect } from 'vitest' |
| | | |
| | | import { mount } from '@vue/test-utils' |
| | | import App from '../App.vue' |
| | | import router from '../router' |
| | | |
| | | describe('App', () => { |
| | | it('mounts renders properly', () => { |
| | | const wrapper = mount(App) |
| | | const wrapper = mount(App, { |
| | | global: { |
| | | plugins: [router], |
| | | }, |
| | | }) |
| | | expect(wrapper.text()).toContain('You did it!') |
| | | }) |
| | | }) |
| 比對新檔案 |
| | |
| | | import { defineRequest, API_MODE } from '@/hooks/useRequest' |
| | | |
| | | const mode = API_MODE.TEST |
| | | |
| | | export function exampleRequest(data: Record<string, unknown>) { |
| | | return defineRequest<{ result: string }>({ |
| | | url: '/example_url', |
| | | method: 'POST', |
| | | mode, |
| | | data, |
| | | }) |
| | | } |
| 比對新檔案 |
| | |
| | | import { describe, it, expect, vi, beforeEach } from 'vitest' |
| | | import { render, screen, waitFor } from '@testing-library/vue' |
| | | import userEvent from '@testing-library/user-event' |
| | | import { createTestingPinia } from '@pinia/testing' |
| | | import { http, HttpResponse, delay } from 'msw' |
| | | import { server } from '@/mocks/node' |
| | | import ExamplePage from './index.vue' |
| | | |
| | | const BASE_API = import.meta.env.VITE_BASE_API |
| | | |
| | | /** |
| | | * ExamplePage 元件測試 |
| | | * |
| | | * 測試策略: |
| | | * - 使用 Testing Library 從使用者角度測試 |
| | | * - 每個測試建立獨立的 Pinia store 實例,避免狀態汙染 |
| | | * - 使用 MSW 模擬 API 回應,並可透過 server.use() 客製化延遲時間 |
| | | * - 專注測試使用者可見的行為,而非實作細節 |
| | | */ |
| | | describe('ExamplePage', () => { |
| | | beforeEach(() => { |
| | | // Arrange - 客製化 MSW handler,加入 1 秒延遲模擬真實 API 請求 |
| | | server.use( |
| | | http.post(`${BASE_API}/example_url`, async () => { |
| | | await delay(1000) |
| | | return HttpResponse.json({ |
| | | data: 'hello world', |
| | | msg: 'success', |
| | | code: '200', |
| | | sysDate: new Date().toISOString(), |
| | | }) |
| | | }), |
| | | ) |
| | | }) |
| | | describe('初始渲染', () => { |
| | | it('should display page title on mount', () => { |
| | | // Arrange |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | |
| | | // Act |
| | | render(ExamplePage, { |
| | | global: { plugins: [testStore] }, |
| | | }) |
| | | |
| | | // Assert |
| | | expect(screen.getAllByText('I am Example Page').length).toBeGreaterThan(0) |
| | | }) |
| | | |
| | | it('should display call request button with correct text', () => { |
| | | // Arrange |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | |
| | | // Act |
| | | render(ExamplePage, { |
| | | global: { plugins: [testStore] }, |
| | | }) |
| | | |
| | | // Assert |
| | | expect( |
| | | screen.getByRole('button', { name: /click to call example request/i }), |
| | | ).toBeInTheDocument() |
| | | }) |
| | | |
| | | it('should not show loading or response messages before interaction', () => { |
| | | // Arrange |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | |
| | | // Act |
| | | render(ExamplePage, { |
| | | global: { plugins: [testStore] }, |
| | | }) |
| | | |
| | | // Assert |
| | | expect(screen.queryByText('fetching...')).not.toBeInTheDocument() |
| | | expect(screen.queryByText('the mock response is:')).not.toBeInTheDocument() |
| | | }) |
| | | }) |
| | | |
| | | describe('當使用者點擊請求按鈕', () => { |
| | | it('should display successful API response data after request completes', async () => { |
| | | // Arrange |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | render(ExamplePage, { |
| | | global: { plugins: [testStore] }, |
| | | }) |
| | | const user = userEvent.setup() |
| | | |
| | | // Act |
| | | const button = screen.getByRole('button', { name: /click to call example request/i }) |
| | | await user.click(button) |
| | | |
| | | await waitFor( |
| | | async () => { |
| | | // Assert - 等待載入完成並顯示結果 |
| | | expect(await screen.findByText('the mock response is:')).toBeInTheDocument() |
| | | }, |
| | | { interval: 1000, timeout: 2000 }, |
| | | ) |
| | | }) |
| | | |
| | | it('should display JSON formatted response data', async () => { |
| | | // Arrange |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | render(ExamplePage, { |
| | | global: { plugins: [testStore] }, |
| | | }) |
| | | const user = userEvent.setup() |
| | | |
| | | // Act |
| | | const button = screen.getByRole('button', { name: /click to call example request/i }) |
| | | await user.click(button) |
| | | |
| | | // Assert - 驗證回應是 JSON 格式 |
| | | await waitFor( |
| | | () => { |
| | | const responseElement = screen.getByText(/"hello world"/i) |
| | | expect(responseElement).toBeInTheDocument() |
| | | }, |
| | | { interval: 1000, timeout: 2000 }, |
| | | ) |
| | | }) |
| | | }) |
| | | |
| | | /** |
| | | * 測試請求進行中的狀態 |
| | | * |
| | | * 使用 server.use() 臨時覆蓋預設的 MSW handler, |
| | | * 加入 1 秒延遲來模擬真實的網路請求情況。 |
| | | * 這讓我們能夠捕捉到 loading 狀態的變化。 |
| | | */ |
| | | describe('請求進行中的狀態', () => { |
| | | it('should show loading indicator immediately after button click', async () => { |
| | | // Arrange |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | render(ExamplePage, { |
| | | global: { plugins: [testStore] }, |
| | | }) |
| | | const user = userEvent.setup() |
| | | |
| | | // Act - 點擊按鈕觸發請求 |
| | | const button = screen.getByRole('button', { name: /click to call example request/i }) |
| | | await user.click(button) |
| | | |
| | | // Assert - 驗證載入狀態出現 |
| | | expect(await screen.findByText('fetching...')).toBeInTheDocument() |
| | | expect(screen.queryByText('the mock response is:')).not.toBeInTheDocument() |
| | | }) |
| | | |
| | | it('should transition from loading to completed state correctly', async () => { |
| | | // Arrange |
| | | const testStore = createTestingPinia({ createSpy: vi.fn }) |
| | | render(ExamplePage, { |
| | | global: { plugins: [testStore] }, |
| | | }) |
| | | const user = userEvent.setup() |
| | | |
| | | // Act - 觸發請求 |
| | | const button = screen.getByRole('button', { name: /click to call example request/i }) |
| | | await user.click(button) |
| | | |
| | | // Assert - 驗證 loading 狀態 |
| | | expect(await screen.findByText('fetching...')).toBeInTheDocument() |
| | | expect(screen.queryByText('the mock response is:')).not.toBeInTheDocument() |
| | | |
| | | // Assert - 等待並驗證完成狀態 |
| | | await waitFor( |
| | | () => { |
| | | expect(screen.queryByText('fetching...')).not.toBeInTheDocument() |
| | | expect(screen.getByText('the mock response is:')).toBeInTheDocument() |
| | | expect(screen.getByText(/"hello world"/i)).toBeInTheDocument() |
| | | }, |
| | | { interval: 1000, timeout: 2000 }, // 給予足夠的時間等待延遲完成 |
| | | ) |
| | | }) |
| | | }) |
| | | }) |
| 比對新檔案 |
| | |
| | | <script setup lang="ts"> |
| | | import { ref } from 'vue' |
| | | import useRequest from '@/hooks/useRequest' |
| | | import { exampleRequest } from './api' |
| | | import cn from '@/utils/cn' |
| | | |
| | | const { apiRequest } = useRequest() |
| | | const text = ref('') |
| | | const loading = ref(false) |
| | | const done = ref(false) |
| | | |
| | | async function handleRequest() { |
| | | loading.value = true |
| | | done.value = false |
| | | text.value = '' |
| | | try { |
| | | const { success, data } = await apiRequest(exampleRequest({})) |
| | | if (success) { |
| | | text.value = JSON.stringify(data) |
| | | } else { |
| | | text.value = 'fetch failed' |
| | | } |
| | | } catch (e) { |
| | | text.value = e as string |
| | | } finally { |
| | | loading.value = false |
| | | done.value = true |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <div>I am Example Page</div> |
| | | <div class="m-2 text-lg"> |
| | | I am Example Page |
| | | <button |
| | | :class="cn('block rounded border bg-blue-100 p-2 hover:outline hover:outline-blue-500')" |
| | | @click="handleRequest" |
| | | > |
| | | click to call example request |
| | | </button> |
| | | <div |
| | | :class=" |
| | | cn('flex w-1/2 flex-col', { |
| | | 'text-green-500': done, |
| | | 'text-blue-500': loading, |
| | | }) |
| | | " |
| | | > |
| | | <p v-if="loading">fetching...</p> |
| | | <p v-if="done">the mock response is:</p> |
| | | <p>{{ text }}</p> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | import { createApp } from 'vue' |
| | | import { createPinia } from 'pinia' |
| | | |
| | | import { worker } from '@/mocks/browser.js' |
| | | import App from './App.vue' |
| | | import router from './router' |
| | | import '@/assets/css/main.css' |
| | | |
| | | if (import.meta.env.VITE_NODE_ENV === 'development') { |
| | | await worker.start({ quiet: true, onUnhandledRequest: 'bypass' }) |
| | | } |
| | | |
| | | const app = createApp(App) |
| | | |
| | | app.use(createPinia()) |
| 比對新檔案 |
| | |
| | | import { setupWorker } from 'msw/browser' |
| | | import { handlers } from './handlers' |
| | | |
| | | export const worker = setupWorker(...handlers) |
| 比對新檔案 |
| | |
| | | const exampleMocks = { |
| | | '/example_url': { |
| | | data: 'hello world', |
| | | msg: 'success', |
| | | code: '200', |
| | | sysDate: new Date().toISOString(), |
| | | }, |
| | | } |
| | | |
| | | export default exampleMocks |
| 比對新檔案 |
| | |
| | | import exampleMocks from './example' |
| | | |
| | | const mockJsons = { |
| | | ...exampleMocks, |
| | | } |
| | | |
| | | export default mockJsons |
| 比對新檔案 |
| | |
| | | import { http, passthrough, HttpResponse } from 'msw' |
| | | import mockJsons from './data' |
| | | import type { HttpResponseResolver } from 'msw' |
| | | |
| | | const BASE_API = import.meta.env.VITE_BASE_API |
| | | |
| | | const baseResolver: HttpResponseResolver = async ({ request }) => { |
| | | if (!request.url.includes('mode=test')) { |
| | | return passthrough() |
| | | } |
| | | const apiDomain = BASE_API || /(.*localhost:\d+)/ |
| | | /** 不包含主網域及參數的路由 */ |
| | | const pathName = request.url |
| | | .replace(apiDomain, '') |
| | | .replace(/\?(.*)/, '') as keyof typeof mockJsons |
| | | const mockData = mockJsons[pathName] |
| | | |
| | | return HttpResponse.json(mockData) |
| | | } |
| | | |
| | | export const handlers = [http.post(`${BASE_API}/example_url`, baseResolver)] |
| 比對新檔案 |
| | |
| | | import { setupServer } from 'msw/node' |
| | | import { handlers } from './handlers' |
| | | |
| | | export const server = setupServer(...handlers) |
| | |
| | | |
| | | const router = createRouter({ |
| | | history: createWebHistory(import.meta.env.BASE_URL), |
| | | routes: [], |
| | | routes: [ |
| | | { |
| | | path: '/', |
| | | redirect: '/example', |
| | | }, |
| | | { |
| | | path: '/example', |
| | | component: () => import('@/features/example/index.vue'), |
| | | }, |
| | | ], |
| | | }) |
| | | |
| | | export default router |
| | |
| | | "exclude": ["src/**/__tests__/*"], |
| | | "compilerOptions": { |
| | | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
| | | |
| | | "types": ["@testing-library/jest-dom"], |
| | | "paths": { |
| | | "@/*": ["./src/*"] |
| | | } |
| | |
| | | environment: 'jsdom', |
| | | exclude: [...configDefaults.exclude, 'e2e/**'], |
| | | root: fileURLToPath(new URL('./', import.meta.url)), |
| | | setupFiles: ['./setupTests.ts'], |
| | | }, |
| | | }), |
| | | ) |