From 15674a9db6c39e7cbbc6a21f519286d0a3476e14 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:06:17 +0800 Subject: [PATCH] test(web): align test state boundaries (#39239) --- web/__tests__/base/chat-flow.test.tsx | 2 +- .../billing/billing-integration.test.tsx | 65 +-- .../billing/cloud-plan-payment-flow.test.tsx | 49 +- .../education-verification-flow.test.tsx | 41 +- .../billing/pricing-modal-flow.test.tsx | 41 +- .../billing/self-hosted-plan-flow.test.tsx | 49 +- web/__tests__/embedded-user-id-store.test.tsx | 15 +- web/__tests__/utils/mock-app-context-state.ts | 234 -------- .../[appId]/__tests__/layout-main.spec.tsx | 60 +- .../overview/__tests__/card-view.spec.tsx | 49 +- .../overview/__tests__/chart-view.spec.tsx | 18 +- .../[appId]/overview/__tests__/view.spec.tsx | 18 +- .../overview/tracing/__tests__/panel.spec.tsx | 18 +- .../__tests__/layout-main.spec.tsx | 98 +--- .../(commonLayout)/datasets/layout.spec.tsx | 74 +-- .../webapp-signin/__tests__/page.spec.tsx | 4 +- .../external-member-sso-auth.spec.tsx | 6 +- .../__tests__/app-detail-section.spec.tsx | 38 +- .../__tests__/dataset-detail-section.spec.tsx | 38 +- .../__tests__/app-info-detail-panel.spec.tsx | 35 +- .../__tests__/dropdown-callbacks.spec.tsx | 38 +- .../dataset-info/__tests__/index.spec.tsx | 38 +- .../snippet-info/__tests__/dropdown.spec.tsx | 33 +- .../access-config/__tests__/index.spec.tsx | 52 +- .../__tests__/access-control.spec.tsx | 17 +- .../add-member-or-group-pop.spec.tsx | 3 +- .../__tests__/index.spec.tsx | 4 +- .../app-publisher/__tests__/index.spec.tsx | 49 +- .../dataset-config/__tests__/index.spec.tsx | 48 +- .../select-dataset/__tests__/index.spec.tsx | 45 +- .../settings-modal/__tests__/index.spec.tsx | 3 +- .../__tests__/chat-item.spec.tsx | 7 +- .../__tests__/index.spec.tsx | 19 +- .../__tests__/use-configuration.spec.tsx | 51 +- .../app-card/__tests__/index.spec.tsx | 2 +- .../app-list/__tests__/index.spec.tsx | 46 +- .../create-app-modal/__tests__/index.spec.tsx | 46 +- .../__tests__/index.spec.tsx | 47 +- .../__tests__/archived-logs-notice.spec.tsx | 12 +- .../app/log/__tests__/empty-element.spec.tsx | 2 +- .../app/log/__tests__/list.spec.tsx | 10 +- .../app/overview/__tests__/app-card.spec.tsx | 4 +- .../overview/__tests__/trigger-card.spec.tsx | 3 +- .../embedded/__tests__/index.spec.tsx | 3 +- .../switch-app-modal/__tests__/index.spec.tsx | 4 +- .../app/workflow-log/__tests__/index.spec.tsx | 4 +- .../apps/__tests__/app-card.spec.tsx | 70 +-- .../apps/__tests__/creators-filter.spec.tsx | 45 +- .../components/apps/__tests__/index.spec.tsx | 45 +- .../components/apps/__tests__/list.spec.tsx | 53 +- .../__tests__/header-in-mobile.spec.tsx | 2 +- .../sidebar/__tests__/index.spec.tsx | 4 +- .../__tests__/tips.spec.tsx | 41 +- .../__tests__/unsubmitted.spec.tsx | 3 +- .../embedded-chatbot/__tests__/index.spec.tsx | 4 +- .../header/__tests__/index.spec.tsx | 4 +- .../billing-page/__tests__/index.spec.tsx | 42 +- .../billing/pricing/__tests__/dialog.spec.tsx | 36 +- .../custom-page/__tests__/index.spec.tsx | 38 +- .../__tests__/use-web-app-brand.spec.tsx | 129 ++--- .../datasets/__tests__/mock-dataset-access.ts | 136 ----- .../access-config/__tests__/index.spec.tsx | 86 +-- .../image-previewer/__tests__/index.spec.tsx | 529 ++++-------------- .../__tests__/built-in-pipeline-list.spec.tsx | 4 +- .../datasets/create/__tests__/index.spec.tsx | 48 +- .../documents/__tests__/index.spec.tsx | 51 +- .../components/__tests__/list.spec.tsx | 18 +- .../document-list/__tests__/index.spec.tsx | 59 +- .../__tests__/document-table-row.spec.tsx | 35 +- ...ocument-actions-with-query-client.spec.tsx | 4 +- .../__tests__/index.spec.tsx | 52 +- .../documents/detail/__tests__/index.spec.tsx | 18 +- .../detail/embedding/__tests__/index.spec.tsx | 4 +- .../__tests__/use-embedding-status.spec.tsx | 4 +- .../api-access/__tests__/card.spec.tsx | 18 +- .../service-api/__tests__/index.spec.tsx | 15 +- .../hit-testing/__tests__/index.spec.tsx | 52 +- .../datasets/list/__tests__/index.spec.tsx | 58 +- .../dataset-card/__tests__/index.spec.tsx | 52 +- .../__tests__/operations-dropdown.spec.tsx | 86 +-- .../base/__tests__/date-picker.spec.tsx | 2 +- .../settings/form/__tests__/index.spec.tsx | 100 +--- .../__tests__/basic-info-section.spec.tsx | 79 +-- .../hooks/__tests__/use-form-state.spec.ts | 47 +- .../__tests__/index.spec.tsx | 132 ++--- .../detail-sidebar/__tests__/index.spec.tsx | 37 +- .../develop/__tests__/index.spec.tsx | 18 +- .../__tests__/secret-key-modal.spec.tsx | 53 +- .../explore/app-card/__tests__/index.spec.tsx | 2 +- .../explore/app-list/__tests__/index.spec.tsx | 48 +- .../explore/banner/__tests__/banner.spec.tsx | 21 +- .../continue-work/__tests__/item.spec.tsx | 48 +- .../create-app-modal/__tests__/index.spec.tsx | 3 +- .../explore/try-app/__tests__/index.spec.tsx | 2 +- .../account-about/__tests__/index.spec.tsx | 16 +- .../account-dropdown/__tests__/index.spec.tsx | 94 ++-- .../account-setting/__tests__/index.spec.tsx | 128 ++--- .../__tests__/access-rule-section.spec.tsx | 39 +- .../__tests__/index.spec.tsx | 39 +- .../__tests__/card.spec.tsx | 39 +- .../__tests__/index.spec.tsx | 29 +- .../__tests__/plugin-actions.spec.tsx | 4 +- .../members-page/__tests__/index.spec.tsx | 78 ++- .../__tests__/invite-button.spec.tsx | 48 +- .../__tests__/member-menu.spec.tsx | 8 +- .../__tests__/index.spec.tsx | 51 +- .../__tests__/index.spec.tsx | 49 +- .../__tests__/index.non-cloud.spec.tsx | 4 +- .../__tests__/index.spec.tsx | 4 +- .../add-credential-in-load-balancing.spec.tsx | 39 +- .../__tests__/add-custom-model.spec.tsx | 39 +- ...itch-credential-in-load-balancing.spec.tsx | 39 +- .../authorized/__tests__/index.spec.tsx | 39 +- .../model-modal/__tests__/dialog.spec.tsx | 18 +- .../model-modal/__tests__/index.spec.tsx | 39 +- .../model-selector/__tests__/index.spec.tsx | 4 +- .../__tests__/popup-item.spec.tsx | 45 +- .../model-selector/__tests__/popup.spec.tsx | 6 +- .../__tests__/credential-panel.spec.tsx | 4 +- .../__tests__/index.spec.tsx | 48 +- .../__tests__/model-list-item.spec.tsx | 39 +- .../__tests__/model-list.spec.tsx | 39 +- .../__tests__/provider-card-actions.spec.tsx | 4 +- .../__tests__/quota-panel.spec.tsx | 4 +- .../use-change-provider-priority.spec.ts | 10 +- .../use-credential-panel-state.spec.ts | 4 +- .../__tests__/api-key-section.spec.tsx | 39 +- .../__tests__/dialog.spec.tsx | 18 +- .../__tests__/dropdown-content.spec.tsx | 39 +- .../__tests__/index.spec.tsx | 39 +- .../permissions-page/__tests__/index.spec.tsx | 39 +- .../role-list/__tests__/index.spec.tsx | 18 +- .../role-list/__tests__/row-menu.spec.tsx | 39 +- .../preference-page/__tests__/index.spec.tsx | 43 +- .../__tests__/index.spec.tsx | 9 +- .../header/env-nav/__tests__/index.spec.tsx | 63 +-- .../license-env/__tests__/index.spec.tsx | 12 +- .../integrations/__tests__/page.spec.tsx | 23 +- .../__tests__/tool-provider-list.spec.tsx | 81 +-- .../main-nav/__tests__/index.spec.tsx | 108 ++-- .../main-nav/__tests__/layout.spec.tsx | 45 +- .../__tests__/support-menu.spec.tsx | 37 +- .../__tests__/workspace-card.spec.tsx | 46 +- .../install-plugin/__tests__/hooks.spec.ts | 177 +++--- .../base/__tests__/use-get-icon.spec.ts | 32 +- .../use-install-plugin-limit.spec.ts | 2 +- ...orkspace-plugin-install-permission.spec.ts | 65 +-- .../install-bundle/__tests__/index.spec.tsx | 2 +- .../steps/__tests__/install-multi.spec.tsx | 2 +- .../__tests__/use-install-multi-state.spec.ts | 2 +- .../steps/__tests__/install.spec.tsx | 45 +- .../steps/__tests__/install.spec.tsx | 47 +- .../marketplace/__tests__/query.spec.tsx | 4 +- .../__tests__/authorized-in-node.spec.tsx | 22 +- .../__tests__/plugin-auth-in-agent.spec.tsx | 47 +- .../__tests__/plugin-auth.spec.tsx | 53 +- .../__tests__/api-key-modal.spec.tsx | 44 +- .../__tests__/authorize-components.spec.tsx | 83 +-- .../authorize/__tests__/index.spec.tsx | 57 +- .../authorized/__tests__/index.spec.tsx | 70 +-- .../authorized/__tests__/item.spec.tsx | 42 +- .../__tests__/operation-dropdown.spec.tsx | 4 +- .../detail-header/__tests__/index.spec.tsx | 6 +- .../__tests__/use-detail-header-state.spec.ts | 4 +- .../plugin-item/__tests__/index.spec.tsx | 40 +- .../__tests__/index.spec.tsx | 10 +- .../__tests__/context-provider.spec.tsx | 8 +- .../plugin-page/__tests__/index.spec.tsx | 74 +-- .../install-plugin-dropdown.spec.tsx | 4 +- .../__tests__/use-reference-setting.spec.ts | 84 ++- .../empty/__tests__/index.spec.tsx | 4 +- .../plugin-tasks/__tests__/index.spec.tsx | 3 +- .../__tests__/index.spec.tsx | 4 +- .../__tests__/index.spec.tsx | 2 +- .../__tests__/from-market-place.spec.tsx | 10 +- .../update-plugin/__tests__/index.spec.tsx | 10 +- .../components/__tests__/conversion.spec.tsx | 18 +- .../__tests__/rag-pipeline-main.spec.tsx | 18 +- .../editor/__tests__/index.spec.tsx | 4 +- .../editor/form/__tests__/index.spec.tsx | 4 +- .../preview/__tests__/index.spec.tsx | 4 +- .../publisher/__tests__/index.spec.tsx | 57 +- .../publisher/__tests__/popup.spec.tsx | 57 +- .../use-text-generation-app-state.spec.ts | 4 +- .../signin/__tests__/countdown.spec.tsx | 6 +- .../snippet-list/__tests__/index.spec.tsx | 56 +- .../__tests__/snippet-card.spec.tsx | 48 +- .../__tests__/snippet-create-button.spec.tsx | 45 +- .../import-snippet-dsl-dialog.spec.tsx | 45 +- .../__tests__/snippet-main.spec.tsx | 50 +- .../__tests__/use-create-snippet.spec.tsx | 45 +- .../tools/mcp/__tests__/create-card.spec.tsx | 41 +- .../tools/mcp/__tests__/index.spec.tsx | 39 +- .../mcp/__tests__/provider-card.spec.tsx | 39 +- .../mcp/detail/__tests__/content.spec.tsx | 45 +- .../__tests__/use-mcp-service-card.spec.ts | 33 +- .../__tests__/custom-create-card.spec.tsx | 41 +- .../tools/provider/__tests__/detail.spec.tsx | 45 +- .../workflow-app/__tests__/index.spec.tsx | 79 +-- .../__tests__/workflow-main.spec.tsx | 18 +- .../__tests__/use-nodes-sync-draft.spec.ts | 4 +- .../hooks/__tests__/use-workflow-init.spec.ts | 48 +- .../__tests__/selection-contextmenu.spec.tsx | 36 +- .../__tests__/workflow-edge-events.spec.tsx | 15 + .../workflow/__tests__/workflow-test-env.tsx | 17 +- .../__tests__/all-start-blocks.spec.tsx | 4 +- .../__tests__/all-tools.spec.tsx | 4 +- .../__tests__/data-sources.spec.tsx | 4 +- .../__tests__/featured-tools.spec.tsx | 5 +- .../__tests__/featured-triggers.spec.tsx | 5 +- .../block-selector/__tests__/tabs.spec.tsx | 4 +- .../__tests__/tool-picker.spec.tsx | 40 +- .../hooks/__tests__/use-collaboration.spec.ts | 6 +- .../workflow/comment/comment-icon.spec.tsx | 61 +- .../workflow/comment/comment-input.spec.tsx | 33 +- .../workflow/comment/thread.spec.tsx | 33 +- .../__tests__/header-in-restoring.spec.tsx | 30 +- .../header/__tests__/header-layouts.spec.tsx | 30 +- .../use-node-plugin-installation.spec.ts | 36 +- .../__tests__/use-workflow-comment.spec.ts | 34 +- .../nodes/_base/__tests__/node.spec.tsx | 34 +- .../__tests__/email-configure-modal.spec.tsx | 33 +- .../__tests__/method-item.spec.tsx | 33 +- .../__tests__/test-email-sender.spec.tsx | 35 +- .../recipient/__tests__/index.spec.tsx | 39 +- .../__tests__/integration.spec.tsx | 45 +- .../__tests__/import-from-tool.spec.tsx | 6 +- .../__tests__/use-config.spec.ts | 6 +- .../__tests__/var-group-item.spec.tsx | 4 + .../__tests__/index.spec.tsx | 15 +- .../operator/__tests__/zoom-in-out.spec.tsx | 4 +- .../__tests__/human-input-form-list.spec.tsx | 3 +- .../comments-panel/__tests__/index.spec.tsx | 33 +- .../__tests__/index.spec.tsx | 33 +- .../workflow/run/__tests__/index.spec.tsx | 5 + .../__tests__/agent-log-item.spec.tsx | 10 +- .../__tests__/agent-result-panel.spec.tsx | 10 +- web/app/install/installForm.spec.tsx | 4 +- web/app/signup/components/input-mail.spec.tsx | 4 +- .../__tests__/console-bootstrap.spec.tsx | 4 +- web/context/modal-context.test.tsx | 40 +- .../__tests__/access-surface-cards.spec.tsx | 62 +- .../__tests__/agent-prompt-editor.spec.tsx | 47 +- .../knowledge/__tests__/index.spec.tsx | 18 +- .../tools/__tests__/index.spec.tsx | 7 + .../preview/__tests__/chat.spec.tsx | 61 +- .../preview/__tests__/versions-panel.spec.tsx | 61 +- .../state/__tests__/index.spec.ts | 175 ++---- .../state/__tests__/dsl-enabled.spec.ts | 211 +++---- .../state/__tests__/index.spec.ts | 286 +++++----- .../state/__tests__/index.spec.ts | 242 +++----- .../detail/__tests__/state.spec.ts | 125 ++--- .../detail/access/__tests__/state.spec.ts | 43 +- .../__tests__/permissions.spec.tsx | 59 +- .../detail/api-tokens/__tests__/state.spec.ts | 43 +- .../__tests__/api-key-generate-menu.spec.tsx | 42 +- .../detail/overview/__tests__/state.spec.ts | 46 +- .../detail/releases/__tests__/state.spec.ts | 73 +-- .../release-actions/__tests__/state.spec.ts | 68 ++- .../__tests__/tag-filter.spec.tsx | 45 +- .../__tests__/tag-management-modal.spec.tsx | 45 +- .../__tests__/tag-search-content.spec.tsx | 45 +- .../__tests__/tag-selector.spec.tsx | 45 +- web/hooks/use-credential-permissions.spec.ts | 38 +- web/hooks/use-document-title.spec.ts | 8 +- web/hooks/use-import-dsl.spec.tsx | 42 +- web/hooks/use-timestamp.spec.ts | 2 +- web/service/__tests__/use-plugins.spec.tsx | 34 +- web/test/account-profile-query.ts | 70 --- web/test/console/account-profile.ts | 85 +++ web/test/console/current-workspace.ts | 45 ++ web/test/console/query-client.ts | 10 + .../console/query-data.ts} | 135 ++++- web/test/console/query-provider.tsx | 32 ++ web/test/console/render.ts | 69 +++ web/test/console/state-fixture.ts | 225 ++++++++ web/test/console/workspace-permissions.ts | 42 ++ web/test/nuqs-testing.tsx | 2 +- web/test/query-atom.ts | 11 + web/test/query-client.ts | 17 + web/vitest.setup.ts | 241 +------- 281 files changed, 3881 insertions(+), 7556 deletions(-) delete mode 100644 web/__tests__/utils/mock-app-context-state.ts delete mode 100644 web/app/components/datasets/__tests__/mock-dataset-access.ts delete mode 100644 web/test/account-profile-query.ts create mode 100644 web/test/console/account-profile.ts create mode 100644 web/test/console/current-workspace.ts create mode 100644 web/test/console/query-client.ts rename web/{__tests__/utils/mock-system-features.tsx => test/console/query-data.ts} (55%) create mode 100644 web/test/console/query-provider.tsx create mode 100644 web/test/console/render.ts create mode 100644 web/test/console/state-fixture.ts create mode 100644 web/test/console/workspace-permissions.ts create mode 100644 web/test/query-atom.ts create mode 100644 web/test/query-client.ts diff --git a/web/__tests__/base/chat-flow.test.tsx b/web/__tests__/base/chat-flow.test.tsx index b8bf3ab2f04..03449c65e67 100644 --- a/web/__tests__/base/chat-flow.test.tsx +++ b/web/__tests__/base/chat-flow.test.tsx @@ -3,12 +3,12 @@ import type { ChatConfig } from '@/app/components/base/chat/types' import type { AppConversationData, AppData, AppMeta, ConversationItem } from '@/models/share' import { fireEvent, renderHook, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' import ChatWithHistory from '@/app/components/base/chat/chat-with-history' import { useChatWithHistory } from '@/app/components/base/chat/chat-with-history/hooks' import { useThemeContext } from '@/app/components/base/chat/embedded-chatbot/theme/theme-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useDocumentTitle from '@/hooks/use-document-title' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' vi.mock('@/app/components/base/chat/chat-with-history/hooks', () => ({ useChatWithHistory: vi.fn(), diff --git a/web/__tests__/billing/billing-integration.test.tsx b/web/__tests__/billing/billing-integration.test.tsx index 7c889cb5dad..22ebdd420a2 100644 --- a/web/__tests__/billing/billing-integration.test.tsx +++ b/web/__tests__/billing/billing-integration.test.tsx @@ -1,5 +1,5 @@ import type { UsagePlanInfo, UsageResetInfo } from '@/app/components/billing/type' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import AnnotationFull from '@/app/components/billing/annotation-full' @@ -14,9 +14,10 @@ import TriggerEventsLimitModal from '@/app/components/billing/trigger-events-lim import { Plan } from '@/app/components/billing/type' import UpgradeBtn from '@/app/components/billing/upgrade-btn' import VectorSpaceFull from '@/app/components/billing/vector-space-full' +import { render } from '@/test/console/render' let mockProviderCtx: Record = {} -let mockAppCtx: Record = {} +let mockConsoleState: Record = {} const mockSetShowPricingModal = vi.fn() const mockSetShowAccountSettingModal = vi.fn() @@ -32,31 +33,21 @@ vi.mock('@/context/provider-context', () => ({ useProviderContext: () => mockProviderCtx, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState) }) vi.mock('@/context/modal-context', () => ({ @@ -149,8 +140,8 @@ const setupProviderContext = ( } } -const setupAppContext = (overrides: Record = {}) => { - mockAppCtx = { +const setupConsoleState = (overrides: Record = {}) => { + mockConsoleState = { isCurrentWorkspaceManager: true, workspacePermissionKeys: ['billing.view', 'billing.manage', 'billing.subscription.manage'], userProfile: { email: 'test@example.com' }, @@ -168,7 +159,7 @@ const setupAppContext = (overrides: Record = {}) => { describe('Billing Page + Plan Integration', () => { beforeEach(() => { vi.clearAllMocks() - setupAppContext() + setupConsoleState() }) // Verify that the billing page renders PlanComp with all 7 usage items @@ -256,7 +247,7 @@ describe('Billing Page + Plan Integration', () => { describe('Billing URL button', () => { it('should show billing button when manager has subscription management permission', () => { setupProviderContext({ type: Plan.sandbox }) - setupAppContext({ + setupConsoleState({ isCurrentWorkspaceManager: true, workspacePermissionKeys: ['billing.subscription.manage'], }) @@ -269,7 +260,7 @@ describe('Billing Page + Plan Integration', () => { it('should hide billing button when subscription management permission is granted without manager role', () => { setupProviderContext({ type: Plan.sandbox }) - setupAppContext({ + setupConsoleState({ isCurrentWorkspaceManager: false, workspacePermissionKeys: ['billing.subscription.manage'], }) @@ -281,7 +272,7 @@ describe('Billing Page + Plan Integration', () => { it('should hide billing button when subscription management permission is missing', () => { setupProviderContext({ type: Plan.sandbox }) - setupAppContext({ + setupConsoleState({ isCurrentWorkspaceManager: true, workspacePermissionKeys: ['billing.view', 'billing.manage'], }) @@ -301,7 +292,7 @@ describe('Billing Page + Plan Integration', () => { it('should hide billing button when no billing permissions are granted', () => { setupProviderContext({ type: Plan.sandbox }) - setupAppContext({ + setupConsoleState({ workspacePermissionKeys: [], }) @@ -319,7 +310,7 @@ describe('Billing Page + Plan Integration', () => { describe('Plan Type Display Integration', () => { beforeEach(() => { vi.clearAllMocks() - setupAppContext() + setupConsoleState() }) it('should render sandbox plan with upgrade button (premium badge)', () => { @@ -385,7 +376,7 @@ describe('Plan Type Display Integration', () => { describe('Upgrade Flow Integration', () => { beforeEach(() => { vi.clearAllMocks() - setupAppContext() + setupConsoleState() setupProviderContext({ type: Plan.sandbox }) }) @@ -532,7 +523,7 @@ describe('Upgrade Flow Integration', () => { describe('Capacity Full Components Integration', () => { beforeEach(() => { vi.clearAllMocks() - setupAppContext() + setupConsoleState() }) // AppsFull renders with correct messaging and components @@ -731,7 +722,7 @@ describe('Capacity Full Components Integration', () => { describe('PriorityLabel Integration', () => { beforeEach(() => { vi.clearAllMocks() - setupAppContext() + setupConsoleState() }) it('should display "standard" priority for sandbox plan', () => { @@ -777,7 +768,7 @@ describe('PriorityLabel Integration', () => { describe('Usage Display Edge Cases', () => { beforeEach(() => { vi.clearAllMocks() - setupAppContext() + setupConsoleState() }) // Vector space storage mode behavior @@ -881,7 +872,7 @@ describe('Usage Display Edge Cases', () => { describe('Cross-Component Upgrade Flow', () => { beforeEach(() => { vi.clearAllMocks() - setupAppContext() + setupConsoleState() }) it('should trigger pricing from AppsFull upgrade button', async () => { diff --git a/web/__tests__/billing/cloud-plan-payment-flow.test.tsx b/web/__tests__/billing/cloud-plan-payment-flow.test.tsx index 066a505e854..fbe9d8f987d 100644 --- a/web/__tests__/billing/cloud-plan-payment-flow.test.tsx +++ b/web/__tests__/billing/cloud-plan-payment-flow.test.tsx @@ -9,47 +9,30 @@ */ import type { BasicPlan } from '@/app/components/billing/type' import { toast, ToastHost } from '@langgenius/dify-ui/toast' -import { cleanup, render, screen, waitFor } from '@testing-library/react' +import { cleanup, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import { ALL_PLANS } from '@/app/components/billing/config' import { PlanRange } from '@/app/components/billing/pricing/plan-switcher/plan-range-switcher' import CloudPlanItem from '@/app/components/billing/pricing/plans/cloud-plan-item' import { Plan } from '@/app/components/billing/type' +import { render } from '@/test/console/render' // ─── Mock state ────────────────────────────────────────────────────────────── -let mockAppCtx: Record = {} +let mockConsoleState: Record = {} const mockFetchSubscriptionUrls = vi.fn() const mockInvoices = vi.fn() const mockOpenAsyncWindow = vi.fn() // ─── Context mocks ─────────────────────────────────────────────────────────── -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) // ─── Service mocks ─────────────────────────────────────────────────────────── @@ -79,8 +62,8 @@ vi.mock('@/next/navigation', () => ({ })) // ─── Helpers ───────────────────────────────────────────────────────────────── -const setupAppContext = (overrides: Record = {}) => { - mockAppCtx = { +const setupConsoleState = (overrides: Record = {}) => { + mockConsoleState = { isCurrentWorkspaceManager: true, workspacePermissionKeys: ['billing.view', 'billing.manage', 'billing.subscription.manage'], ...overrides, @@ -116,7 +99,7 @@ describe('Cloud Plan Payment Flow', () => { vi.clearAllMocks() cleanup() toast.dismiss() - setupAppContext() + setupConsoleState() mockFetchSubscriptionUrls.mockResolvedValue({ url: 'https://pay.example.com/checkout' }) mockInvoices.mockResolvedValue({ url: 'https://billing.example.com/invoices' }) }) @@ -295,7 +278,7 @@ describe('Cloud Plan Payment Flow', () => { // ─── 5. Permission Check ──────────────────────────────────────────────── describe('Permission check', () => { it('should change plans when billing manage permission is granted without manager role', async () => { - setupAppContext({ + setupConsoleState({ isCurrentWorkspaceManager: false, workspacePermissionKeys: ['billing.manage'], }) @@ -311,7 +294,7 @@ describe('Cloud Plan Payment Flow', () => { }) it('should show error toast when billing manage permission is missing for plan changes', async () => { - setupAppContext({ + setupConsoleState({ isCurrentWorkspaceManager: false, workspacePermissionKeys: ['billing.view', 'billing.subscription.manage'], }) @@ -328,7 +311,7 @@ describe('Cloud Plan Payment Flow', () => { }) it('should open billing portal when subscription management permission is granted without manager role', async () => { - setupAppContext({ + setupConsoleState({ isCurrentWorkspaceManager: false, workspacePermissionKeys: ['billing.subscription.manage'], }) @@ -345,7 +328,7 @@ describe('Cloud Plan Payment Flow', () => { }) it('should show error toast when subscription management permission is missing for current paid plan', async () => { - setupAppContext({ + setupConsoleState({ isCurrentWorkspaceManager: false, workspacePermissionKeys: ['billing.view', 'billing.manage'], }) diff --git a/web/__tests__/billing/education-verification-flow.test.tsx b/web/__tests__/billing/education-verification-flow.test.tsx index c24bb7ab5c4..79a346a1fc6 100644 --- a/web/__tests__/billing/education-verification-flow.test.tsx +++ b/web/__tests__/billing/education-verification-flow.test.tsx @@ -8,16 +8,17 @@ * Also covers education button visibility based on context flags. */ import type { UsagePlanInfo, UsageResetInfo } from '@/app/components/billing/type' -import { cleanup, render, screen, waitFor } from '@testing-library/react' +import { cleanup, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import { defaultPlan } from '@/app/components/billing/config' import PlanComp from '@/app/components/billing/plan' import { Plan } from '@/app/components/billing/type' +import { render } from '@/test/console/render' // ─── Mock state ────────────────────────────────────────────────────────────── let mockProviderCtx: Record = {} -let mockAppCtx: Record = {} +let mockConsoleState: Record = {} const mockSetShowPricingModal = vi.fn() const mockSetShowAccountSettingModal = vi.fn() const mockRouterPush = vi.fn() @@ -37,31 +38,21 @@ vi.mock('@/context/provider-context', () => ({ useProviderContext: () => mockProviderCtx, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState) }) vi.mock('@/context/modal-context', () => ({ @@ -164,7 +155,7 @@ const setupContexts = ( allowRefreshEducationVerify: false, ...providerOverrides, } - mockAppCtx = { + mockConsoleState = { isCurrentWorkspaceManager: true, workspacePermissionKeys: ['billing.view', 'billing.manage', 'billing.subscription.manage'], userProfile: { email: 'student@university.edu' }, diff --git a/web/__tests__/billing/pricing-modal-flow.test.tsx b/web/__tests__/billing/pricing-modal-flow.test.tsx index 08c2f85df06..595eac8ef53 100644 --- a/web/__tests__/billing/pricing-modal-flow.test.tsx +++ b/web/__tests__/billing/pricing-modal-flow.test.tsx @@ -8,47 +8,38 @@ * Validates cross-component state propagation when the user switches between * cloud / self-hosted categories and monthly / yearly plan ranges. */ -import { cleanup, render, screen } from '@testing-library/react' +import { cleanup, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import { ALL_PLANS } from '@/app/components/billing/config' import Pricing from '@/app/components/billing/pricing' import { Plan } from '@/app/components/billing/type' +import { render } from '@/test/console/render' // ─── Mock state ────────────────────────────────────────────────────────────── let mockProviderCtx: Record = {} -let mockAppCtx: Record = {} +let mockConsoleState: Record = {} // ─── Context mocks ─────────────────────────────────────────────────────────── vi.mock('@/context/provider-context', () => ({ useProviderContext: () => mockProviderCtx, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState) }) vi.mock('@/context/i18n', () => ({ @@ -138,7 +129,7 @@ const setupContexts = ( isEducationAccount: false, allowRefreshEducationVerify: false, } - mockAppCtx = { + mockConsoleState = { isCurrentWorkspaceManager: true, workspacePermissionKeys: ['billing.view', 'billing.manage', 'billing.subscription.manage'], userProfile: { email: 'test@example.com' }, diff --git a/web/__tests__/billing/self-hosted-plan-flow.test.tsx b/web/__tests__/billing/self-hosted-plan-flow.test.tsx index 1051be5b959..3be64c06f6b 100644 --- a/web/__tests__/billing/self-hosted-plan-flow.test.tsx +++ b/web/__tests__/billing/self-hosted-plan-flow.test.tsx @@ -8,7 +8,7 @@ import { toast, ToastHost } from '@langgenius/dify-ui/toast' * Covers community/premium/enterprise plan rendering, external URL navigation, * and workspace manager permission enforcement. */ -import { cleanup, render, screen, waitFor } from '@testing-library/react' +import { cleanup, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import { @@ -18,37 +18,20 @@ import { } from '@/app/components/billing/config' import SelfHostedPlanItem from '@/app/components/billing/pricing/plans/self-hosted-plan-item' import { SelfHostedPlan } from '@/app/components/billing/type' +import { render } from '@/test/console/render' -let mockAppCtx: Record = {} +let mockConsoleState: Record = {} const originalLocation = window.location let assignedHref = '' -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) vi.mock('@/hooks/use-theme', () => ({ @@ -69,8 +52,8 @@ vi.mock('@/app/components/billing/pricing/plans/self-hosted-plan-item/list', () ), })) -const setupAppContext = (overrides: Record = {}) => { - mockAppCtx = { +const setupConsoleState = (overrides: Record = {}) => { + mockConsoleState = { isCurrentWorkspaceManager: true, workspacePermissionKeys: ['billing.manage'], ...overrides, @@ -91,7 +74,7 @@ describe('Self-Hosted Plan Flow', () => { vi.clearAllMocks() cleanup() toast.dismiss() - setupAppContext() + setupConsoleState() // Mock window.location with minimal getter/setter (Location props are non-enumerable) assignedHref = '' @@ -208,7 +191,7 @@ describe('Self-Hosted Plan Flow', () => { // ─── 3. Permission Check ──────────────────────────────────────────────── describe('Permission check', () => { it('should redirect when billing manage permission is granted without manager role', async () => { - setupAppContext({ + setupConsoleState({ isCurrentWorkspaceManager: false, workspacePermissionKeys: ['billing.manage'], }) @@ -222,7 +205,7 @@ describe('Self-Hosted Plan Flow', () => { }) it('should show error toast when billing manage permission is missing for community button', async () => { - setupAppContext({ workspacePermissionKeys: [] }) + setupConsoleState({ workspacePermissionKeys: [] }) const user = userEvent.setup() renderSelfHostedPlanItem(SelfHostedPlan.community) @@ -237,7 +220,7 @@ describe('Self-Hosted Plan Flow', () => { }) it('should show error toast when billing manage permission is missing for premium button', async () => { - setupAppContext({ workspacePermissionKeys: [] }) + setupConsoleState({ workspacePermissionKeys: [] }) const user = userEvent.setup() renderSelfHostedPlanItem(SelfHostedPlan.premium) @@ -251,7 +234,7 @@ describe('Self-Hosted Plan Flow', () => { }) it('should show error toast when billing manage permission is missing for enterprise button', async () => { - setupAppContext({ workspacePermissionKeys: [] }) + setupConsoleState({ workspacePermissionKeys: [] }) const user = userEvent.setup() renderSelfHostedPlanItem(SelfHostedPlan.enterprise) diff --git a/web/__tests__/embedded-user-id-store.test.tsx b/web/__tests__/embedded-user-id-store.test.tsx index 5e69feabf97..96795923776 100644 --- a/web/__tests__/embedded-user-id-store.test.tsx +++ b/web/__tests__/embedded-user-id-store.test.tsx @@ -1,12 +1,9 @@ import { screen, waitFor } from '@testing-library/react' import * as React from 'react' import { renderToString } from 'react-dom/server' -import { - createSystemFeaturesWrapper, - renderWithSystemFeatures, -} from '@/__tests__/utils/mock-system-features' import WebAppStoreProvider, { useWebAppStore } from '@/context/web-app-context' import { AccessMode } from '@/models/access-control' +import { createConsoleQueryWrapper, renderWithConsoleQuery } from '@/test/console/query-data' const navigationMocks = vi.hoisted(() => ({ usePathname: vi.fn(() => '/chatbot/sample-app'), @@ -89,7 +86,7 @@ describe('WebAppStoreProvider embedded user id handling', () => { configurable: true, value: undefined, }) - const { wrapper: Wrapper } = createSystemFeaturesWrapper() + const { wrapper: Wrapper } = createConsoleQueryWrapper() try { expect(() => @@ -118,7 +115,7 @@ describe('WebAppStoreProvider embedded user id handling', () => { navigationMocks.useSearchParams.mockReturnValue(params) mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({}) - renderWithSystemFeatures( + renderWithConsoleQuery( , @@ -133,7 +130,7 @@ describe('WebAppStoreProvider embedded user id handling', () => { navigationMocks.usePathname.mockReturnValue(pathname) mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({}) - renderWithSystemFeatures( + renderWithConsoleQuery( , @@ -149,7 +146,7 @@ describe('WebAppStoreProvider embedded user id handling', () => { conversation_id: 'conversation-456', }) - renderWithSystemFeatures( + renderWithConsoleQuery( , @@ -171,7 +168,7 @@ describe('WebAppStoreProvider embedded user id handling', () => { })) mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({}) - renderWithSystemFeatures( + renderWithConsoleQuery( , diff --git a/web/__tests__/utils/mock-app-context-state.ts b/web/__tests__/utils/mock-app-context-state.ts deleted file mode 100644 index 9d4409cb42e..00000000000 --- a/web/__tests__/utils/mock-app-context-state.ts +++ /dev/null @@ -1,234 +0,0 @@ -import type { LangGeniusVersionInfo } from '@/context/app-context-types' -import type { ICurrentWorkspace } from '@/models/common' - -const APP_CONTEXT_STATE_ATOM_KIND = Symbol('app-context-state-atom-kind') - -export type AppContextStateMockState = { - userProfile?: { - id?: string - name?: string - email?: string - avatar?: string | null - avatar_url?: string | null - is_password_set?: boolean - } | null - currentWorkspace?: - | ({ - id?: string - name?: string - } & Partial) - | null - isCurrentWorkspaceManager?: boolean - isCurrentWorkspaceOwner?: boolean - isCurrentWorkspaceEditor?: boolean - isCurrentWorkspaceDatasetOperator?: boolean - isLoadingCurrentWorkspace?: boolean - isLoadingWorkspacePermissionKeys?: boolean - workspacePermissionKeys?: string[] - datasetRbacEnabled?: boolean - langGeniusVersionInfo?: Partial - refreshUserProfile?: () => void - refreshCurrentWorkspace?: () => void - mutateUserProfile?: () => void - mutateCurrentWorkspace?: () => void -} - -type AppContextStateAtomKind = - | 'userProfile' - | 'userProfileId' - | 'userProfileEmail' - | 'currentWorkspace' - | 'currentWorkspaceId' - | 'isCurrentWorkspaceManager' - | 'isCurrentWorkspaceOwner' - | 'isCurrentWorkspaceEditor' - | 'isCurrentWorkspaceDatasetOperator' - | 'currentWorkspaceLoading' - | 'workspacePermissionKeys' - | 'workspacePermissionKeysLoading' - | 'datasetRbacEnabled' - | 'langGeniusVersionInfo' - | 'langGeniusCurrentVersion' - | 'refreshUserProfile' - | 'refreshCurrentWorkspace' - -type AppContextStateMockAtom = { - [APP_CONTEXT_STATE_ATOM_KIND]: AppContextStateAtomKind -} - -type AppContextStateMockRegistry = { - getState: () => AppContextStateMockState -} - -const defaultUserProfile = { - id: 'user-1', - name: 'User', - email: 'user@example.com', - avatar: '', - avatar_url: '', - is_password_set: true, -} - -const defaultCurrentWorkspace = { - id: 'workspace-1', - name: 'Workspace', - plan: '', - status: '', - created_at: 0, - role: 'owner', - providers: [], - trial_credits: 0, - trial_credits_used: 0, - trial_credits_exhausted_at: 0, - next_credit_reset_date: 0, -} satisfies ICurrentWorkspace - -const defaultLangGeniusVersionInfo = { - current_env: 'CLOUD', - current_version: '', - latest_version: '', - version: '', - release_date: '', - release_notes: '', - features: { - can_replace_logo: false, - model_load_balancing_enabled: false, - }, - can_auto_update: false, -} satisfies LangGeniusVersionInfo - -let appContextStateMockRegistry: AppContextStateMockRegistry | undefined - -const createMockAtom = (kind: AppContextStateAtomKind): AppContextStateMockAtom => ({ - [APP_CONTEXT_STATE_ATOM_KIND]: kind, -}) - -const isAppContextStateMockAtom = (atom: unknown): atom is AppContextStateMockAtom => { - return typeof atom === 'object' && atom !== null && APP_CONTEXT_STATE_ATOM_KIND in atom -} - -const getUserProfile = (state: AppContextStateMockState) => ({ - ...defaultUserProfile, - ...state.userProfile, -}) - -const getCurrentWorkspace = (state: AppContextStateMockState): ICurrentWorkspace => ({ - ...defaultCurrentWorkspace, - ...state.currentWorkspace, -}) - -const getLangGeniusVersionInfo = (state: AppContextStateMockState): LangGeniusVersionInfo => ({ - ...defaultLangGeniusVersionInfo, - ...state.langGeniusVersionInfo, -}) - -export const createAppContextStateAtomMock = async ( - importOriginal: () => Promise, - getState: () => AppContextStateMockState, -) => { - const actual = await importOriginal() - appContextStateMockRegistry = { - getState, - } - - return { - ...actual, - userProfileAtom: createMockAtom('userProfile'), - userProfileIdAtom: createMockAtom('userProfileId'), - userProfileEmailAtom: createMockAtom('userProfileEmail'), - currentWorkspaceAtom: createMockAtom('currentWorkspace'), - currentWorkspaceIdAtom: createMockAtom('currentWorkspaceId'), - isCurrentWorkspaceManagerAtom: createMockAtom('isCurrentWorkspaceManager'), - isCurrentWorkspaceOwnerAtom: createMockAtom('isCurrentWorkspaceOwner'), - isCurrentWorkspaceEditorAtom: createMockAtom('isCurrentWorkspaceEditor'), - isCurrentWorkspaceDatasetOperatorAtom: createMockAtom('isCurrentWorkspaceDatasetOperator'), - currentWorkspaceLoadingAtom: createMockAtom('currentWorkspaceLoading'), - workspacePermissionKeysAtom: createMockAtom('workspacePermissionKeys'), - workspacePermissionKeysLoadingAtom: createMockAtom('workspacePermissionKeysLoading'), - datasetRbacEnabledAtom: createMockAtom('datasetRbacEnabled'), - langGeniusVersionInfoAtom: createMockAtom('langGeniusVersionInfo'), - langGeniusCurrentVersionAtom: createMockAtom('langGeniusCurrentVersion'), - refreshUserProfileAtom: createMockAtom('refreshUserProfile'), - refreshCurrentWorkspaceAtom: createMockAtom('refreshCurrentWorkspace'), - } -} - -export const createAppContextStateJotaiMock = async (importOriginal: () => Promise) => { - const actual = await importOriginal() - - return { - ...actual, - useAtomValue: (atom: unknown) => { - if (!isAppContextStateMockAtom(atom)) - return actual.useAtomValue(atom as Parameters[0]) - - if (!appContextStateMockRegistry) - throw new Error('App context state atom mock is not initialized') - - const state = appContextStateMockRegistry.getState() - const userProfile = getUserProfile(state) - const currentWorkspace = getCurrentWorkspace(state) - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'userProfile') return userProfile - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'userProfileId') return userProfile.id - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'userProfileEmail') return userProfile.email - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'currentWorkspace') return currentWorkspace - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'currentWorkspaceId') return currentWorkspace.id - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'isCurrentWorkspaceManager') - return state.isCurrentWorkspaceManager ?? false - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'isCurrentWorkspaceOwner') - return state.isCurrentWorkspaceOwner ?? false - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'isCurrentWorkspaceEditor') - return state.isCurrentWorkspaceEditor ?? false - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'isCurrentWorkspaceDatasetOperator') - return state.isCurrentWorkspaceDatasetOperator ?? false - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'currentWorkspaceLoading') - return state.isLoadingCurrentWorkspace ?? false - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'workspacePermissionKeys') - return state.workspacePermissionKeys ?? [] - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'workspacePermissionKeysLoading') - return state.isLoadingWorkspacePermissionKeys ?? false - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'datasetRbacEnabled') - return state.datasetRbacEnabled ?? false - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'langGeniusVersionInfo') - return getLangGeniusVersionInfo(state) - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'langGeniusCurrentVersion') - return getLangGeniusVersionInfo(state).current_version - - throw new Error(`Unsupported app context state atom: ${atom[APP_CONTEXT_STATE_ATOM_KIND]}`) - }, - useSetAtom: (atom: unknown) => { - if (!isAppContextStateMockAtom(atom)) - return actual.useSetAtom(atom as Parameters[0]) - - if (!appContextStateMockRegistry) - throw new Error('App context state atom mock is not initialized') - - const state = appContextStateMockRegistry.getState() - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'refreshUserProfile') - return state.refreshUserProfile ?? (() => {}) - - if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'refreshCurrentWorkspace') - return state.refreshCurrentWorkspace ?? (() => {}) - - throw new Error( - `Unsupported app context state write atom: ${atom[APP_CONTEXT_STATE_ATOM_KIND]}`, - ) - }, - } -} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/__tests__/layout-main.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/__tests__/layout-main.spec.tsx index dc0f9fab6b9..37e7aeb814e 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/__tests__/layout-main.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/__tests__/layout-main.spec.tsx @@ -1,9 +1,9 @@ import type { App } from '@/types/app' import { screen, waitFor } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useStore } from '@/app/components/app/store' import { usePathname, useRouter } from '@/next/navigation' import { fetchAppDetailDirect } from '@/service/apps' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import AppDetailLayout from '../layout-main' @@ -11,7 +11,7 @@ import AppDetailLayout from '../layout-main' const mockReplace = vi.fn() let mockPathname = '/app/app-1/workflow' let mockIsRbacEnabled = true -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ currentWorkspace: { id: 'workspace-1' }, isLoadingCurrentWorkspace: false, isLoadingWorkspacePermissionKeys: false, @@ -19,8 +19,8 @@ const mockAppContextState = vi.hoisted(() => ({ workspacePermissionKeys: [] as string[], })) -const render = (ui: Parameters[0]) => - renderWithSystemFeatures(ui, { +const render = (ui: Parameters[0]) => + renderWithConsoleQuery(ui, { systemFeatures: { rbac_enabled: mockIsRbacEnabled, }, @@ -35,37 +35,17 @@ vi.mock('@/service/apps', () => ({ fetchAppDetailDirect: vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) vi.mock('@/hooks/use-document-title', () => ({ @@ -96,11 +76,11 @@ describe('AppDetailLayout', () => { vi.clearAllMocks() mockPathname = '/app/app-1/workflow' mockIsRbacEnabled = true - mockAppContextState.currentWorkspace = { id: 'workspace-1' } - mockAppContextState.isLoadingCurrentWorkspace = false - mockAppContextState.isLoadingWorkspacePermissionKeys = false - mockAppContextState.userProfile = { id: 'user-1' } - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.currentWorkspace = { id: 'workspace-1' } + mockConsoleState.isLoadingCurrentWorkspace = false + mockConsoleState.isLoadingWorkspacePermissionKeys = false + mockConsoleState.userProfile = { id: 'user-1' } + mockConsoleState.workspacePermissionKeys = [] mockUsePathname.mockImplementation(() => mockPathname) mockUseRouter.mockReturnValue({ back: vi.fn(), @@ -266,7 +246,7 @@ describe('AppDetailLayout', () => { }) it('should wait for workspace permission keys before redirecting restricted pages', async () => { - mockAppContextState.isLoadingWorkspacePermissionKeys = true + mockConsoleState.isLoadingWorkspacePermissionKeys = true mockPathname = '/app/app-1/overview' mockFetchAppDetailDirect.mockResolvedValue( createAppDetail({ permission_keys: [AppACLPermission.ViewLayout] }), @@ -284,7 +264,7 @@ describe('AppDetailLayout', () => { expect(mockReplace).not.toHaveBeenCalled() expect(screen.queryByText('App page content')).not.toBeInTheDocument() - mockAppContextState.isLoadingWorkspacePermissionKeys = false + mockConsoleState.isLoadingWorkspacePermissionKeys = false rerender(
App page content
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/card-view.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/card-view.spec.tsx index 8ad439eb2a1..04c3c07986e 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/card-view.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/card-view.spec.tsx @@ -1,6 +1,7 @@ import type { App } from '@/types/app' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import CardView from '../card-view' const mockAppState = vi.hoisted(() => ({ @@ -44,58 +45,30 @@ vi.mock('@tanstack/react-query', async (importOriginal) => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'user-1' }, currentWorkspace: { id: 'workspace-1' }, workspacePermissionKeys: mockAppState.appDetail.permission_keys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ userProfile: { id: 'user-1' }, currentWorkspace: { id: 'workspace-1' }, workspacePermissionKeys: mockAppState.appDetail.permission_keys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'user-1' }, currentWorkspace: { id: 'workspace-1' }, workspacePermissionKeys: mockAppState.appDetail.permission_keys, })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-1' }, - currentWorkspace: { id: 'workspace-1' }, - workspacePermissionKeys: mockAppState.appDetail.permission_keys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-1' }, - currentWorkspace: { id: 'workspace-1' }, - workspacePermissionKeys: mockAppState.appDetail.permission_keys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/app/components/workflow/collaboration/core/collaboration-manager', () => ({ collaborationManager: { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/chart-view.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/chart-view.spec.tsx index 587a069744f..89102a1386a 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/chart-view.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/chart-view.spec.tsx @@ -1,4 +1,5 @@ -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { AppACLPermission } from '@/utils/permission' import ChartView from '../chart-view' @@ -14,6 +15,13 @@ const testState = vi.hoisted(() => ({ chartRenderSpy: vi.fn(), })) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/app/components/app/store', () => ({ useStore: (selector: (state: { appDetail: typeof testState.appDetail }) => T): T => selector({ @@ -84,6 +92,14 @@ vi.mock('../time-range-picker', () => ({ default: () => , })) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('ChartView monitor permission', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/view.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/view.spec.tsx index 97e70cc592c..145a638b135 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/view.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/view.spec.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from 'react' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { AppACLPermission } from '@/utils/permission' import OverviewView from '../view' @@ -14,6 +15,13 @@ const testState = vi.hoisted(() => ({ workspacePermissionKeys: [] as string[], })) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/app/components/app/store', () => ({ useStore: (selector: (state: { appDetail: typeof testState.appDetail }) => T): T => selector({ @@ -38,6 +46,14 @@ vi.mock('../tracing/panel', () => ({ default: () => , })) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('OverviewView monitor permission', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/panel.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/panel.spec.tsx index 65699442045..a70f7ab2be5 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/panel.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/panel.spec.tsx @@ -1,6 +1,7 @@ import type { ComponentProps, ReactNode } from 'react' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import { fetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { AppACLPermission } from '@/utils/permission' import Panel from '../panel' @@ -13,6 +14,13 @@ const testState = vi.hoisted(() => ({ }>, })) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/next/navigation', () => ({ usePathname: () => '/app/app-1/overview', })) @@ -91,6 +99,14 @@ const renderPanel = async () => { await screen.findAllByTestId('config-button') } +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('Tracing overview panel permissions', () => { beforeEach(() => { testState.appPermissionKeys = [] diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/__tests__/layout-main.spec.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/__tests__/layout-main.spec.tsx index 29c3c4d43bf..08d1082ded3 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/__tests__/layout-main.spec.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/__tests__/layout-main.spec.tsx @@ -1,15 +1,15 @@ import { screen, waitFor } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { usePathname, useRouter } from '@/next/navigation' import { useDatasetDetail } from '@/service/knowledge/use-dataset' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { DatasetACLPermission } from '@/utils/permission' import DatasetDetailLayout from '../layout-main' const mockReplace = vi.fn() let mockIsRbacEnabled = true -const render = (ui: Parameters[0]) => - renderWithSystemFeatures(ui, { +const render = (ui: Parameters[0]) => + renderWithConsoleQuery(ui, { systemFeatures: { rbac_enabled: mockIsRbacEnabled, }, @@ -24,80 +24,31 @@ vi.mock('@/service/knowledge/use-dataset', () => ({ useDatasetDetail: vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: [], - }), - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createAccountStateModuleMock(() => ({ + userProfile: { id: 'user-1' }, + })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: [], - }), - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createWorkspaceStateModuleMock(() => ({})) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: [], - }), - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: [], - }), - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock( - importOriginal, - () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: [], - }), - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createSystemFeaturesStateModuleMock(() => ({ + datasetRbacEnabled: mockIsRbacEnabled, + })) }) vi.mock('@/context/event-emitter', () => ({ @@ -106,13 +57,6 @@ vi.mock('@/context/event-emitter', () => ({ }), })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - vi.mock('@/hooks/use-document-title', () => ({ default: vi.fn(), })) diff --git a/web/app/(commonLayout)/datasets/layout.spec.tsx b/web/app/(commonLayout)/datasets/layout.spec.tsx index 6510d4f299b..9272e9f9b11 100644 --- a/web/app/(commonLayout)/datasets/layout.spec.tsx +++ b/web/app/(commonLayout)/datasets/layout.spec.tsx @@ -1,10 +1,11 @@ import type { ReactNode } from 'react' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import DatasetsLayout from './layout' const mockReplace = vi.fn() -const mockUseAppContext = vi.fn() +const mockConsoleStateReader = vi.fn() let mockPathname = '/datasets' let mockExternalKnowledgeApiProviderEnabled: boolean | undefined @@ -15,48 +16,21 @@ vi.mock('@/next/navigation', () => ({ usePathname: () => mockPathname, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockUseAppContext()) + return createWorkspaceStateModuleMock(() => mockConsoleStateReader()) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockUseAppContext()) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => mockUseAppContext()) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => mockUseAppContext()) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => mockUseAppContext()) + return createPermissionStateModuleMock(() => mockConsoleStateReader()) }) vi.mock('@/context/external-api-panel-context', () => ({ ExternalApiPanelProvider: ({ children }: { children: ReactNode }) => <>{children}, })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - vi.mock('@/context/external-knowledge-api-context', () => ({ ExternalKnowledgeApiProvider: ({ children, @@ -70,7 +44,7 @@ vi.mock('@/context/external-knowledge-api-context', () => ({ }, })) -type AppContextMock = { +type ConsoleStateFixture = { isCurrentWorkspaceEditor: boolean isCurrentWorkspaceDatasetOperator: boolean isLoadingCurrentWorkspace: boolean @@ -81,7 +55,7 @@ type AppContextMock = { } } -const baseContext: AppContextMock = { +const baseContext: ConsoleStateFixture = { isCurrentWorkspaceEditor: true, isCurrentWorkspaceDatasetOperator: false, isLoadingCurrentWorkspace: false, @@ -92,8 +66,8 @@ const baseContext: AppContextMock = { }, } -const setAppContext = (overrides: Partial = {}) => { - mockUseAppContext.mockReturnValue({ +const setConsoleState = (overrides: Partial = {}) => { + mockConsoleStateReader.mockReturnValue({ ...baseContext, ...overrides, }) @@ -104,11 +78,11 @@ describe('DatasetsLayout', () => { vi.clearAllMocks() mockPathname = '/datasets' mockExternalKnowledgeApiProviderEnabled = undefined - setAppContext() + setConsoleState() }) it('should render loading when workspace is still loading', () => { - setAppContext({ + setConsoleState({ isLoadingCurrentWorkspace: true, currentWorkspace: { id: '' }, }) @@ -125,7 +99,7 @@ describe('DatasetsLayout', () => { }) it('should render loading while workspace permission keys are loading', () => { - setAppContext({ + setConsoleState({ isLoadingWorkspacePermissionKeys: true, workspacePermissionKeys: [], }) @@ -142,7 +116,7 @@ describe('DatasetsLayout', () => { }) it('should render children without a page-level dataset permission', () => { - setAppContext({ + setConsoleState({ isCurrentWorkspaceEditor: true, isCurrentWorkspaceDatasetOperator: true, workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'], @@ -159,7 +133,7 @@ describe('DatasetsLayout', () => { }) it('should render children on the dataset list route without dataset permissions', () => { - setAppContext({ + setConsoleState({ isCurrentWorkspaceEditor: false, isCurrentWorkspaceDatasetOperator: false, workspacePermissionKeys: [], @@ -179,7 +153,7 @@ describe('DatasetsLayout', () => { 'should redirect direct dataset creation route to /datasets without dataset.create_and_management: %s', async (pathname) => { mockPathname = pathname - setAppContext({ + setConsoleState({ workspacePermissionKeys: [], }) @@ -198,7 +172,7 @@ describe('DatasetsLayout', () => { it('should render direct dataset creation route when workspace has dataset.create_and_management', () => { mockPathname = '/datasets/create' - setAppContext({ + setConsoleState({ workspacePermissionKeys: ['dataset.create_and_management'], }) @@ -214,7 +188,7 @@ describe('DatasetsLayout', () => { it('should redirect direct external dataset connection route to /datasets without dataset.external.connect', async () => { mockPathname = '/datasets/connect' - setAppContext({ + setConsoleState({ workspacePermissionKeys: [], }) @@ -232,7 +206,7 @@ describe('DatasetsLayout', () => { it('should render direct external dataset connection route when workspace has dataset.external.connect', () => { mockPathname = '/datasets/connect' - setAppContext({ + setConsoleState({ workspacePermissionKeys: ['dataset.external.connect'], }) @@ -247,7 +221,7 @@ describe('DatasetsLayout', () => { }) it('should disable external knowledge API queries without dataset.external.connect', () => { - setAppContext({ + setConsoleState({ workspacePermissionKeys: [], }) @@ -261,7 +235,7 @@ describe('DatasetsLayout', () => { }) it('should enable external knowledge API queries with dataset.external.connect', () => { - setAppContext({ + setConsoleState({ workspacePermissionKeys: ['dataset.external.connect'], }) diff --git a/web/app/(shareLayout)/webapp-signin/__tests__/page.spec.tsx b/web/app/(shareLayout)/webapp-signin/__tests__/page.spec.tsx index d5d897569cc..d791330d78d 100644 --- a/web/app/(shareLayout)/webapp-signin/__tests__/page.spec.tsx +++ b/web/app/(shareLayout)/webapp-signin/__tests__/page.spec.tsx @@ -1,6 +1,6 @@ import { waitFor } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { AccessMode } from '@/models/access-control' +import { renderWithConsoleQuery } from '@/test/console/query-data' import WebSSOForm from '../page' const navigationMocks = vi.hoisted(() => ({ @@ -35,7 +35,7 @@ describe('WebSSOForm redirect security', () => { }) it('should use the login fallback when the redirect target is external', async () => { - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { webapp_auth: { enabled: true } }, }) diff --git a/web/app/(shareLayout)/webapp-signin/components/__tests__/external-member-sso-auth.spec.tsx b/web/app/(shareLayout)/webapp-signin/components/__tests__/external-member-sso-auth.spec.tsx index d144e9f335a..8d35179829f 100644 --- a/web/app/(shareLayout)/webapp-signin/components/__tests__/external-member-sso-auth.spec.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/__tests__/external-member-sso-auth.spec.tsx @@ -1,6 +1,6 @@ import { waitFor } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { SSOProtocol } from '@/features/system-features/constants' +import { renderWithConsoleQuery } from '@/test/console/query-data' import ExternalMemberSSOAuth from '../external-member-sso-auth' const navigationMocks = vi.hoisted(() => ({ @@ -34,7 +34,7 @@ describe('ExternalMemberSSOAuth redirect security', () => { }) it('should use the login fallback without calling SSO when the redirect target is external', async () => { - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { webapp_auth: { sso_config: { protocol: SSOProtocol.SAML } }, }, @@ -58,7 +58,7 @@ describe('ExternalMemberSSOAuth redirect security', () => { }) serviceMock.mockResolvedValue({ url: 'https://idp.example/authorize' }) - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { webapp_auth: { sso_config: { protocol } } }, }) diff --git a/web/app/components/app-sidebar/__tests__/app-detail-section.spec.tsx b/web/app/components/app-sidebar/__tests__/app-detail-section.spec.tsx index ac46a88f2b6..fa5a3849170 100644 --- a/web/app/components/app-sidebar/__tests__/app-detail-section.spec.tsx +++ b/web/app/components/app-sidebar/__tests__/app-detail-section.spec.tsx @@ -1,5 +1,5 @@ import { screen } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { AppACLPermission } from '@/utils/permission' import AppDetailSection from '../app-detail-section' @@ -7,15 +7,15 @@ let mockAppMode = 'chat' let mockPathname = '/app/app-1/logs' let mockAppPermissionKeys: string[] = [] let mockIsRbacEnabled = true -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], }, })) -const render = (ui: Parameters[0]) => - renderWithSystemFeatures(ui, { +const render = (ui: Parameters[0]) => + renderWithConsoleQuery(ui, { systemFeatures: { rbac_enabled: mockIsRbacEnabled, }, @@ -36,31 +36,13 @@ vi.mock('@/app/components/app/store', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current) }) vi.mock('@/next/navigation', () => ({ diff --git a/web/app/components/app-sidebar/__tests__/dataset-detail-section.spec.tsx b/web/app/components/app-sidebar/__tests__/dataset-detail-section.spec.tsx index 5cae00290ae..8cf4eb5db27 100644 --- a/web/app/components/app-sidebar/__tests__/dataset-detail-section.spec.tsx +++ b/web/app/components/app-sidebar/__tests__/dataset-detail-section.spec.tsx @@ -1,6 +1,6 @@ import type { DataSet, RelatedAppResponse } from '@/models/datasets' import { screen } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { DatasetACLPermission } from '@/utils/permission' import DatasetDetailSection from '../dataset-detail-section' @@ -8,15 +8,15 @@ let mockPathname = '/datasets/dataset-1/documents' let mockDataset: DataSet | undefined let mockRelatedApps: RelatedAppResponse | undefined let mockIsRbacEnabled = true -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], }, })) -const render = (ui: Parameters[0]) => - renderWithSystemFeatures(ui, { +const render = (ui: Parameters[0]) => + renderWithConsoleQuery(ui, { systemFeatures: { rbac_enabled: mockIsRbacEnabled, }, @@ -26,31 +26,13 @@ vi.mock('@/next/navigation', () => ({ usePathname: () => mockPathname, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current) }) vi.mock('@/service/knowledge/use-dataset', () => ({ diff --git a/web/app/components/app-sidebar/app-info/__tests__/app-info-detail-panel.spec.tsx b/web/app/components/app-sidebar/app-info/__tests__/app-info-detail-panel.spec.tsx index 98f79869769..23bd0fabcb5 100644 --- a/web/app/components/app-sidebar/app-info/__tests__/app-info-detail-panel.spec.tsx +++ b/web/app/components/app-sidebar/app-info/__tests__/app-info-detail-panel.spec.tsx @@ -1,7 +1,8 @@ import type { App, AppSSO } from '@/types/app' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' +import { render } from '@/test/console/render' import { AppModeEnum } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import AppInfoDetailPanel from '../app-info-detail-panel' @@ -9,7 +10,7 @@ import AppInfoDetailPanel from '../app-info-detail-panel' const mockWorkspacePermissionKeys = vi.hoisted(() => ({ value: ['app.create_and_management'] as string[], })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { userProfile: { id: 'user-1' }, get workspacePermissionKeys() { @@ -18,31 +19,13 @@ const mockAppContextState = vi.hoisted(() => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current) }) vi.mock('../../../base/app-icon', () => ({ diff --git a/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx b/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx index df9bc9badd1..cb3979a6323 100644 --- a/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx +++ b/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx @@ -2,8 +2,8 @@ import type { DataSet } from '@/models/datasets' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { RETRIEVE_METHOD } from '@/types/app' import { DatasetACLPermission } from '@/utils/permission' import Dropdown from '../dropdown' @@ -17,15 +17,15 @@ const mockCheckIsUsedInApp = vi.fn() const mockDeleteDataset = vi.fn() const mockToast = vi.fn() let mockIsRbacEnabled = true -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], }, })) -const render = (ui: Parameters[0]) => - renderWithSystemFeatures(ui, { +const render = (ui: Parameters[0]) => + renderWithConsoleQuery(ui, { systemFeatures: { rbac_enabled: mockIsRbacEnabled, }, @@ -106,31 +106,13 @@ vi.mock('@/context/dataset-detail', () => ({ selector({ dataset: mockDataset }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current) }) vi.mock('@/service/knowledge/use-dataset', () => ({ diff --git a/web/app/components/app-sidebar/dataset-info/__tests__/index.spec.tsx b/web/app/components/app-sidebar/dataset-info/__tests__/index.spec.tsx index 518fa218a02..933e0dd76e0 100644 --- a/web/app/components/app-sidebar/dataset-info/__tests__/index.spec.tsx +++ b/web/app/components/app-sidebar/dataset-info/__tests__/index.spec.tsx @@ -2,8 +2,8 @@ import type { DataSet } from '@/models/datasets' import { createEvent, fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { RETRIEVE_METHOD } from '@/types/app' import { DatasetACLPermission } from '@/utils/permission' import DatasetInfo from '..' @@ -21,15 +21,15 @@ const mockCheckIsUsedInApp = vi.fn() const mockDeleteDataset = vi.fn() const TestEditIcon = () => let mockIsRbacEnabled = true -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], }, })) -const render = (ui: Parameters[0]) => - renderWithSystemFeatures(ui, { +const render = (ui: Parameters[0]) => + renderWithConsoleQuery(ui, { systemFeatures: { rbac_enabled: mockIsRbacEnabled, }, @@ -119,31 +119,13 @@ vi.mock('@/context/dataset-detail', () => ({ selector({ dataset: mockDataset }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current) }) vi.mock('@/service/knowledge/use-dataset', () => ({ diff --git a/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx b/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx index 94f60eb26a2..a10c856fad4 100644 --- a/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx +++ b/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx @@ -1,8 +1,9 @@ import type { CreateSnippetDialogPayload } from '@/app/components/snippets/create-snippet-dialog' import type { SnippetDetail } from '@/models/snippet' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' +import { render } from '@/test/console/render' import SnippetInfoDropdown from '../dropdown' const mockReplace = vi.fn() @@ -15,7 +16,7 @@ const mockDeleteMutate = vi.fn() let mockWorkspacePermissionKeys: string[] = ['snippets.create_and_modify', 'snippets.management'] let mockDropdownOpen = false let mockDropdownOnOpenChange: ((open: boolean) => void) | undefined -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { get workspacePermissionKeys() { return mockWorkspacePermissionKeys @@ -23,31 +24,9 @@ const mockAppContextState = vi.hoisted(() => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current) }) vi.mock('@/next/navigation', () => ({ diff --git a/web/app/components/app/access-config/__tests__/index.spec.tsx b/web/app/components/app/access-config/__tests__/index.spec.tsx index 2d6fd26007f..3d4b7615fdf 100644 --- a/web/app/components/app/access-config/__tests__/index.spec.tsx +++ b/web/app/components/app/access-config/__tests__/index.spec.tsx @@ -1,23 +1,23 @@ import type { AccessRulesEditorProps } from '@/app/components/access-rules-editor' import { screen } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useStore } from '@/app/components/app/store' import { useAppAccessRules, useAppUserAccessSettings, } from '@/service/access-control/use-app-access-config' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { AppACLPermission } from '@/utils/permission' import AppAccessConfigPage from '../index' -const mockAppContext = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], })) let mockIsRbacEnabled = true -const render = (ui: Parameters[0]) => - renderWithSystemFeatures(ui, { +const render = (ui: Parameters[0]) => + renderWithConsoleQuery(ui, { systemFeatures: { rbac_enabled: mockIsRbacEnabled, }, @@ -44,37 +44,13 @@ const mockMutations = vi.hoisted(() => ({ removeMemberBindings: vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) vi.mock('@/service/access-control/use-app-access-config', () => ({ @@ -110,8 +86,8 @@ vi.mock('@/app/components/access-rules-editor', () => ({ describe('AppAccessConfigPage', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContext.userProfile = { id: 'user-1' } - mockAppContext.workspacePermissionKeys = [] + mockConsoleState.userProfile = { id: 'user-1' } + mockConsoleState.workspacePermissionKeys = [] mockIsRbacEnabled = true mockAppAccessRules.items = [] mockAppAccessRules.isLoading = false @@ -258,8 +234,8 @@ describe('AppAccessConfigPage', () => { }) it('should allow the app maintainer with app management workspace permission', () => { - mockAppContext.userProfile = { id: 'account-1' } - mockAppContext.workspacePermissionKeys = ['app.create_and_management'] + mockConsoleState.userProfile = { id: 'account-1' } + mockConsoleState.workspacePermissionKeys = ['app.create_and_management'] useStore.setState({ appDetail: { id: 'app-1', diff --git a/web/app/components/app/app-access-control/__tests__/access-control.spec.tsx b/web/app/components/app/app-access-control/__tests__/access-control.spec.tsx index 0d96d732b16..5f873d951ed 100644 --- a/web/app/components/app/app-access-control/__tests__/access-control.spec.tsx +++ b/web/app/components/app/app-access-control/__tests__/access-control.spec.tsx @@ -3,9 +3,9 @@ import type { App } from '@/types/app' import { toast } from '@langgenius/dify-ui/toast' import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' import useAccessControlStore from '@/context/access-control-store' import { AccessMode, SubjectType } from '@/models/access-control' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import AccessControlDialog from '../access-control-dialog' import AccessControlItem from '../access-control-item' import AddMemberOrGroupDialog from '../add-member-or-group-pop' @@ -30,6 +30,21 @@ vi.mock('@/service/access-control', () => ({ useUpdateAccessMode: () => mockUseUpdateAccessMode(), })) +vi.mock('@/context/account-state', async () => { + const { atom } = await vi.importActual('jotai') + return { + userProfileAtom: atom({ + id: 'user-1', + name: 'Test User', + email: 'test@dify.ai', + avatar: '', + avatar_url: null, + is_password_set: false, + timezone: 'UTC', + }), + } +}) + vi.mock('ahooks', async (importOriginal) => { const actual = await importOriginal() return { diff --git a/web/app/components/app/app-access-control/__tests__/add-member-or-group-pop.spec.tsx b/web/app/components/app/app-access-control/__tests__/add-member-or-group-pop.spec.tsx index 3cc43ea6314..d0a3d256587 100644 --- a/web/app/components/app/app-access-control/__tests__/add-member-or-group-pop.spec.tsx +++ b/web/app/components/app/app-access-control/__tests__/add-member-or-group-pop.spec.tsx @@ -1,8 +1,9 @@ import type { AccessControlAccount, AccessControlGroup, Subject } from '@/models/access-control' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import useAccessControlStore from '@/context/access-control-store' import { SubjectType } from '@/models/access-control' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import AddMemberOrGroupDialog from '../add-member-or-group-pop' const mockUseSearchForWhiteListCandidates = vi.fn() diff --git a/web/app/components/app/app-access-control/__tests__/index.spec.tsx b/web/app/components/app/app-access-control/__tests__/index.spec.tsx index 497bf26d867..dcc8a05fb73 100644 --- a/web/app/components/app/app-access-control/__tests__/index.spec.tsx +++ b/web/app/components/app/app-access-control/__tests__/index.spec.tsx @@ -2,9 +2,9 @@ import type { ReactElement } from 'react' import type { App } from '@/types/app' import { toast } from '@langgenius/dify-ui/toast' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import useAccessControlStore from '@/context/access-control-store' import { AccessMode } from '@/models/access-control' +import { renderWithConsoleQuery } from '@/test/console/query-data' import AccessControl from '../index' let mockWebappAuth = { @@ -15,7 +15,7 @@ let mockWebappAuth = { } const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { webapp_auth: mockWebappAuth }, }) diff --git a/web/app/components/app/app-publisher/__tests__/index.spec.tsx b/web/app/components/app/app-publisher/__tests__/index.spec.tsx index dda5adde69e..ae5cf4a4e9f 100644 --- a/web/app/components/app/app-publisher/__tests__/index.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/index.spec.tsx @@ -1,14 +1,14 @@ /* oxlint-disable typescript/no-explicit-any */ import { fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { AccessMode } from '@/models/access-control' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import { basePath } from '@/utils/var' import { AppPublisher } from '../index' const render = (ui: React.ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { webapp_auth: { enabled: true } }, }) @@ -107,47 +107,20 @@ vi.mock('@/service/use-tools', () => ({ useInvalidateWorkflowToolDetailByAppID: () => vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ isCurrentWorkspaceManager: true, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ isCurrentWorkspaceManager: true, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: true, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: true, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: true, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@langgenius/dify-ui/toast', () => ({ toast: { @@ -610,7 +583,7 @@ describe('AppPublisher', () => { }) const windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null) - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { webapp_auth: { enabled: true }, enable_creators_platform: true }, }) @@ -631,7 +604,7 @@ describe('AppPublisher', () => { it('should show toast error when publish to marketplace fails', async () => { mockPublishToCreatorsPlatform.mockRejectedValue(new Error('network error')) - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { webapp_auth: { enabled: true }, enable_creators_platform: true }, }) @@ -646,7 +619,7 @@ describe('AppPublisher', () => { }) it('should disable marketplace button when not yet published', () => { - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { webapp_auth: { enabled: true }, enable_creators_platform: true }, }) diff --git a/web/app/components/app/configuration/dataset-config/__tests__/index.spec.tsx b/web/app/components/app/configuration/dataset-config/__tests__/index.spec.tsx index 3f8aa021ef2..d5d451b4b99 100644 --- a/web/app/components/app/configuration/dataset-config/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/__tests__/index.spec.tsx @@ -1,7 +1,7 @@ /* oxlint-disable typescript/no-explicit-any */ import type { DataSet } from '@/models/datasets' import type { DatasetConfigs } from '@/models/debug' -import { fireEvent, render, screen, within } from '@testing-library/react' +import { fireEvent, screen, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useContext } from 'use-context-selector' import { @@ -10,6 +10,7 @@ import { } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import { getSelectedDatasetsMode } from '@/app/components/workflow/nodes/knowledge-retrieval/utils' import { DatasetPermission, DataSourceType } from '@/models/datasets' +import { render } from '@/test/console/render' import { AppModeEnum, ModelModeType, RETRIEVE_TYPE } from '@/types/app' import { DatasetACLPermission, getDatasetACLCapabilities } from '@/utils/permission' import DatasetConfig from '../index' @@ -40,53 +41,20 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () })), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'user-123' }, workspacePermissionKeys: [], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'user-123' }, workspacePermissionKeys: [], })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-123' }, - workspacePermissionKeys: [], - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-123' }, - workspacePermissionKeys: [], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-123' }, - workspacePermissionKeys: [], - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/utils/permission', () => ({ DatasetACLPermission: { diff --git a/web/app/components/app/configuration/dataset-config/select-dataset/__tests__/index.spec.tsx b/web/app/components/app/configuration/dataset-config/select-dataset/__tests__/index.spec.tsx index 47e56ba00ac..c4b627ab9cb 100644 --- a/web/app/components/app/configuration/dataset-config/select-dataset/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/select-dataset/__tests__/index.spec.tsx @@ -1,10 +1,11 @@ /* oxlint-disable typescript/no-explicit-any */ import type { DataSet } from '@/models/datasets' -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { describe, expect, it, vi } from 'vitest' import { IndexingType } from '@/app/components/datasets/create/step-two' import { DatasetPermission } from '@/models/datasets' +import { render } from '@/test/console/render' import { RETRIEVE_METHOD } from '@/types/app' import SelectDataSet from '../index' @@ -34,48 +35,12 @@ vi.mock('@/service/knowledge/use-dataset', () => ({ let mockWorkspacePermissionKeys = ['dataset.create_and_management'] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/hooks/use-knowledge', () => ({ useKnowledge: () => ({ diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/index.spec.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/index.spec.tsx index 3fd37828c57..8d67aaf85f0 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/index.spec.tsx @@ -2,7 +2,7 @@ import type { MockedFunction } from 'vitest' import type { DataSet } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { IndexingType } from '@/app/components/datasets/create/step-two' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' @@ -17,6 +17,7 @@ import { } from '@/models/datasets' import { updateDatasetSetting } from '@/service/datasets' import { useMembers } from '@/service/use-common' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { RETRIEVE_METHOD } from '@/types/app' import { DatasetACLPermission } from '@/utils/permission' import SettingsModal from '../index' diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/chat-item.spec.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/chat-item.spec.tsx index 34312524a5c..8d88c235a5d 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/chat-item.spec.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/chat-item.spec.tsx @@ -1,12 +1,13 @@ import type { ModelAndParameter } from '../../types' import type { ChatConfig, ChatItem as ChatItemType, OnSend } from '@/app/components/base/chat/types' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import { TransferMethod } from '@/app/components/base/chat/types' import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { APP_CHAT_WITH_MULTIPLE_MODEL, APP_CHAT_WITH_MULTIPLE_MODEL_RESTART } from '../../types' import ChatItem from '../chat-item' -const mockUseAppContext = vi.fn() +const mockConsoleStateReader = vi.fn() const mockUseDebugConfigurationContext = vi.fn() const mockUseProviderContext = vi.fn() const mockUseFeatures = vi.fn() @@ -116,7 +117,7 @@ const createModelAndParameter = ( }) const createDefaultMocks = () => { - mockUseAppContext.mockReturnValue({ + mockConsoleStateReader.mockReturnValue({ userProfile: { avatar_url: 'http://avatar.url', name: 'Test User' }, }) diff --git a/web/app/components/app/configuration/debug/debug-with-single-model/__tests__/index.spec.tsx b/web/app/components/app/configuration/debug/debug-with-single-model/__tests__/index.spec.tsx index c4c7b38ef64..dfe38c7cc0b 100644 --- a/web/app/components/app/configuration/debug/debug-with-single-model/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/debug/debug-with-single-model/__tests__/index.spec.tsx @@ -6,7 +6,7 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { Collection } from '@/app/components/tools/types' import type { ProviderContextState } from '@/context/provider-context' import type { DatasetConfigs, ModelConfig } from '@/models/debug' -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, screen, waitFor } from '@testing-library/react' import { createRef } from 'react' import { useStore as useAppStore } from '@/app/components/app/store' import { @@ -17,6 +17,7 @@ import { } from '@/app/components/header/account-setting/model-provider-page/declarations' import { CollectionType } from '@/app/components/tools/types' import { PromptMode } from '@/models/debug' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { AgentStrategy, AppModeEnum, ModelModeType, Resolution, TransferMethod } from '@/types/app' import DebugWithSingleModel from '../index' @@ -307,7 +308,7 @@ vi.mock('@/context/provider-context', () => ({ useProviderContext: mockUseProviderContext, })) -const mockAppContext = { +const mockConsoleState = { userProfile: { id: 'user-1', avatar_url: 'https://example.com/avatar.png', @@ -317,14 +318,14 @@ const mockAppContext = { isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, isCurrentWorkspaceDatasetOperator: false, - mutateUserProfile: vi.fn(), + refreshUserProfile: vi.fn(), } -const { mockUseAppContext } = vi.hoisted(() => ({ - mockUseAppContext: vi.fn(), +const { mockConsoleStateReader } = vi.hoisted(() => ({ + mockConsoleStateReader: vi.fn(), })) -mockUseAppContext.mockReturnValue(mockAppContext) +mockConsoleStateReader.mockReturnValue(mockConsoleState) type FeatureState = { moreLikeThis: { enabled: boolean } @@ -602,7 +603,7 @@ describe('DebugWithSingleModel', () => { // Reset mock implementations using module-level mocks mockUseDebugConfigurationContext.mockReturnValue(mockDebugConfigContext) mockUseProviderContext.mockReturnValue(mockProviderContext) - mockUseAppContext.mockReturnValue(mockAppContext) + mockConsoleStateReader.mockReturnValue(mockConsoleState) mockUseConfigFromDebugContext.mockReturnValue(mockConfigFromDebugContext) mockUseFormattingChangedSubscription.mockReturnValue(undefined) mockFeaturesState = { ...defaultFeatures } @@ -936,8 +937,8 @@ describe('DebugWithSingleModel', () => { }) it('should handle missing user profile', () => { - mockUseAppContext.mockReturnValue({ - ...mockAppContext, + mockConsoleStateReader.mockReturnValue({ + ...mockConsoleState, userProfile: { id: '', avatar_url: '', diff --git a/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx b/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx index 39136ab580d..6e47677e660 100644 --- a/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx +++ b/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx @@ -1,6 +1,7 @@ /* oxlint-disable typescript/no-explicit-any */ -import { act, renderHook, waitFor } from '@testing-library/react' +import { act, waitFor } from '@testing-library/react' import { updateAppModelConfig } from '@/service/apps' +import { renderHook } from '@/test/console/render' import { AppModeEnum, ModelModeType } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import { useConfiguration } from '../use-configuration' @@ -44,63 +45,33 @@ vi.mock('ahooks', async () => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ currentWorkspace: { id: 'workspace-1' }, isLoadingCurrentWorkspace: false, userProfile: { id: 'user-1' }, workspacePermissionKeys: ['app.create_and_management'], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ currentWorkspace: { id: 'workspace-1' }, isLoadingCurrentWorkspace: false, userProfile: { id: 'user-1' }, workspacePermissionKeys: ['app.create_and_management'], })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ currentWorkspace: { id: 'workspace-1' }, isLoadingCurrentWorkspace: false, userProfile: { id: 'user-1' }, workspacePermissionKeys: ['app.create_and_management'], })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-1' }, - isLoadingCurrentWorkspace: false, - userProfile: { id: 'user-1' }, - workspacePermissionKeys: ['app.create_and_management'], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-1' }, - isLoadingCurrentWorkspace: false, - userProfile: { id: 'user-1' }, - workspacePermissionKeys: ['app.create_and_management'], - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/modal-context', () => ({ useModalContext: () => ({ diff --git a/web/app/components/app/create-app-dialog/app-card/__tests__/index.spec.tsx b/web/app/components/app/create-app-dialog/app-card/__tests__/index.spec.tsx index bf8f0df5107..365292547a4 100644 --- a/web/app/components/app/create-app-dialog/app-card/__tests__/index.spec.tsx +++ b/web/app/components/app/create-app-dialog/app-card/__tests__/index.spec.tsx @@ -2,9 +2,9 @@ import type { App } from '@/models/explore' import type { AppIconType } from '@/types/app' import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' import { trackEvent } from '@/app/components/base/amplitude' import AppListContext from '@/context/app-list-context' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import AppCard from '../index' diff --git a/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx b/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx index 76d8425b7ec..9eee697e5c0 100644 --- a/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx +++ b/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ import { fireEvent, screen, waitFor } from '@testing-library/react' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import Apps from '../index' @@ -29,53 +29,21 @@ vi.mock('ahooks', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: mockUserProfile, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: mockUserProfile, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('nuqs', () => ({ useQueryState: () => ['Recommended', vi.fn()], })) diff --git a/web/app/components/app/create-app-modal/__tests__/index.spec.tsx b/web/app/components/app/create-app-modal/__tests__/index.spec.tsx index a3736ecca09..2607994156d 100644 --- a/web/app/components/app/create-app-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/create-app-modal/__tests__/index.spec.tsx @@ -1,11 +1,11 @@ import type { App } from '@/types/app' import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage' import { useProviderContext } from '@/context/provider-context' import { useRouter } from '@/next/navigation' import { createApp } from '@/service/apps' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import { getRedirection } from '@/utils/app-redirection' import { trackCreateApp } from '@/utils/create-app-tracking' @@ -15,11 +15,11 @@ const ahooksMocks = vi.hoisted(() => ({ keyPressHandlers: [] as Array<() => void>, })) const mockInvalidateAppList = vi.hoisted(() => vi.fn()) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['app.create_and_management'] as string[], })) -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) vi.mock('ahooks', () => ({ useDebounceFn: unknown>(fn: T) => { @@ -75,37 +75,15 @@ vi.mock('@/utils/app-redirection', () => ({ vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/i18n', () => ({ useDocLink: () => () => '/guides', })) @@ -163,12 +141,12 @@ describe('CreateAppModal', () => { }, enableBilling: true, } as unknown as ReturnType) - mockUseAppContext.mockReturnValue({ + mockConsoleStateReader.mockReturnValue({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['app.create_and_management'], }) - mockAppContextState.userProfile = { id: 'user-1' } - mockAppContextState.workspacePermissionKeys = ['app.create_and_management'] + mockConsoleState.userProfile = { id: 'user-1' } + mockConsoleState.workspacePermissionKeys = ['app.create_and_management'] mockSetItem.mockClear() Object.defineProperty(window, 'localStorage', { value: { diff --git a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx index 5e65dec9adc..968e20e1c34 100644 --- a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx @@ -1,8 +1,8 @@ /* oxlint-disable typescript/no-explicit-any */ import { act, fireEvent, screen, waitFor } from '@testing-library/react' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage' import { DSLImportMode, DSLImportStatus } from '@/models/app' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import CreateFromDSLModal from '../index' import { CreateFromDSLModalTab } from '../types' @@ -76,53 +76,20 @@ vi.mock('@/app/components/workflow/plugin-dependency/hooks', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: mockUserProfile, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: mockUserProfile, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/provider-context', () => ({ useProviderContext: () => ({ diff --git a/web/app/components/app/log/__tests__/archived-logs-notice.spec.tsx b/web/app/components/app/log/__tests__/archived-logs-notice.spec.tsx index c4623c1a30e..1c23d5ab6c0 100644 --- a/web/app/components/app/log/__tests__/archived-logs-notice.spec.tsx +++ b/web/app/components/app/log/__tests__/archived-logs-notice.spec.tsx @@ -1,10 +1,11 @@ -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { createMockProviderContextValue } from '@/__mocks__/provider-context' import { defaultPlan } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useModalContextSelector } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' +import { render } from '@/test/console/render' import { ArchivedLogsNotice } from '../archived-logs-notice' vi.mock('@/config', async (importOriginal) => { @@ -15,8 +16,13 @@ vi.mock('@/config', async (importOriginal) => { } }) -vi.mock('@/context/workspace-state', () => ({ isCurrentWorkspaceManagerAtom: {} })) -vi.mock('jotai', () => ({ useAtomValue: () => true })) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + + return createWorkspaceStateModuleMock(() => ({ + isCurrentWorkspaceManager: true, + })) +}) vi.mock('@/context/provider-context', async (importOriginal) => { const actual = await importOriginal() diff --git a/web/app/components/app/log/__tests__/empty-element.spec.tsx b/web/app/components/app/log/__tests__/empty-element.spec.tsx index f3b087f2f93..26b34d57119 100644 --- a/web/app/components/app/log/__tests__/empty-element.spec.tsx +++ b/web/app/components/app/log/__tests__/empty-element.spec.tsx @@ -1,6 +1,6 @@ import type { App } from '@/types/app' import { screen } from '@testing-library/react' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import EmptyElement from '../empty-element' diff --git a/web/app/components/app/log/__tests__/list.spec.tsx b/web/app/components/app/log/__tests__/list.spec.tsx index 9be03b14a49..a1923cc68e9 100644 --- a/web/app/components/app/log/__tests__/list.spec.tsx +++ b/web/app/components/app/log/__tests__/list.spec.tsx @@ -1,10 +1,8 @@ /* oxlint-disable typescript/no-explicit-any */ import type { ReactNode } from 'react' import { act, fireEvent, screen, waitFor } from '@testing-library/react' -import { - AccountProfileQueryProvider, - createAccountProfileQueryClient, -} from '@/test/account-profile-query' +import { createAccountProfileQueryClient } from '@/test/console/account-profile' +import { QueryClientTestProvider } from '@/test/console/query-provider' import { renderWithNuqs } from '@/test/nuqs-testing' import { AppModeEnum } from '@/types/app' import ConversationList from '../list' @@ -258,9 +256,9 @@ const renderConversationList = ({ } = {}) => { const queryClient = createAccountProfileQueryClient({ timezone: 'Asia/Shanghai' }) return renderWithNuqs( - + - , + , { searchParams }, ) } diff --git a/web/app/components/app/overview/__tests__/app-card.spec.tsx b/web/app/components/app/overview/__tests__/app-card.spec.tsx index f3b2f79ed08..91ba6d15e72 100644 --- a/web/app/components/app/overview/__tests__/app-card.spec.tsx +++ b/web/app/components/app/overview/__tests__/app-card.spec.tsx @@ -1,9 +1,9 @@ import type { ReactElement } from 'react' import type { AppDetailResponse } from '@/models/app' import { fireEvent, screen, waitFor, within } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { InputVarType } from '@/app/components/workflow/types' import { AccessMode } from '@/models/access-control' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import { basePath } from '@/utils/var' @@ -14,7 +14,7 @@ vi.mock('@/context/i18n', () => ({ })) const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { webapp_auth: { enabled: true } }, }) diff --git a/web/app/components/app/overview/__tests__/trigger-card.spec.tsx b/web/app/components/app/overview/__tests__/trigger-card.spec.tsx index 6f81d3941eb..3e0d268af7a 100644 --- a/web/app/components/app/overview/__tests__/trigger-card.spec.tsx +++ b/web/app/components/app/overview/__tests__/trigger-card.spec.tsx @@ -1,5 +1,6 @@ import type { AppDetailResponse } from '@/models/app' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { AppModeEnum } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import TriggerCard from '../trigger-card' diff --git a/web/app/components/app/overview/embedded/__tests__/index.spec.tsx b/web/app/components/app/overview/embedded/__tests__/index.spec.tsx index 7ca3ace81a3..8bea2d6d14b 100644 --- a/web/app/components/app/overview/embedded/__tests__/index.spec.tsx +++ b/web/app/components/app/overview/embedded/__tests__/index.spec.tsx @@ -1,10 +1,11 @@ import type { SiteInfo } from '@/models/share' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import copy from 'copy-to-clipboard' import * as React from 'react' import { act } from 'react' import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest' import { InputVarType } from '@/app/components/workflow/types' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import Embedded from '../index' vi.mock('../style.module.css', () => ({ diff --git a/web/app/components/app/switch-app-modal/__tests__/index.spec.tsx b/web/app/components/app/switch-app-modal/__tests__/index.spec.tsx index 075c27955c6..42f2d84e2f5 100644 --- a/web/app/components/app/switch-app-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/switch-app-modal/__tests__/index.spec.tsx @@ -2,10 +2,10 @@ import type { App } from '@/types/app' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' import { useStore as useAppStore } from '@/app/components/app/store' import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage' import { Plan } from '@/app/components/billing/type' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import SwitchAppModal from '../index' @@ -284,7 +284,7 @@ describe('SwitchAppModal', () => { expect(onSuccess).toHaveBeenCalledTimes(1) expect(onClose).toHaveBeenCalledTimes(1) expect(notify).toHaveBeenCalledWith({ type: 'success', message: 'app.newApp.appCreated' }) - expect(localStorage.setItem).toHaveBeenCalledWith(NEED_REFRESH_APP_LIST_KEY, '1') + expect(localStorage.getItem(NEED_REFRESH_APP_LIST_KEY)).toBe('1') expect(mockPush).toHaveBeenCalledWith('/app/new-app-001/workflow') expect(mockReplace).not.toHaveBeenCalled() }) diff --git a/web/app/components/app/workflow-log/__tests__/index.spec.tsx b/web/app/components/app/workflow-log/__tests__/index.spec.tsx index e4748e93719..96c347183cc 100644 --- a/web/app/components/app/workflow-log/__tests__/index.spec.tsx +++ b/web/app/components/app/workflow-log/__tests__/index.spec.tsx @@ -20,10 +20,10 @@ import type { WorkflowAppLogDetail, WorkflowLogsResponse, WorkflowRunDetail } fr import type { App, AppIconType, AppModeEnum } from '@/types/app' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { APP_PAGE_LIMIT } from '@/config' import { WorkflowRunTriggeredFrom } from '@/models/log' import * as useLogModule from '@/service/use-log' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { TIME_PERIOD_MAPPING } from '../filter' import Logs from '../index' @@ -93,7 +93,7 @@ const mockedUseWorkflowLogs = useLogModule.useWorkflowLogs as MockedFunction< // ============================================================================ const renderWithQueryClient = (ui: React.ReactElement) => { - return renderWithSystemFeatures(ui) + return renderWithConsoleQuery(ui) } // ============================================================================ diff --git a/web/app/components/apps/__tests__/app-card.spec.tsx b/web/app/components/apps/__tests__/app-card.spec.tsx index 5cd9a708ab8..ba2747392c9 100644 --- a/web/app/components/apps/__tests__/app-card.spec.tsx +++ b/web/app/components/apps/__tests__/app-card.spec.tsx @@ -2,11 +2,11 @@ import type { Mock } from 'vitest' import type { App } from '@/types/app' import { fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { AccessMode } from '@/models/access-control' import * as appsService from '@/service/apps' import * as exploreService from '@/service/explore' import * as workflowService from '@/service/workflow' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import { AppCard } from '../app-card' @@ -20,7 +20,7 @@ const mockUserCanAccessApp = vi.hoisted(() => ({ })) const render = (ui: React.ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { webapp_auth: { enabled: mockWebappAuthEnabled }, branding: { enabled: false }, @@ -80,7 +80,7 @@ vi.mock('use-context-selector', () => ({ }), })) -const mockAppContext = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ isCurrentWorkspaceEditor: true, userProfile: { id: 'user-1' }, workspacePermissionKeys: ['app.create_and_management'] as string[], @@ -88,37 +88,17 @@ const mockAppContext = vi.hoisted(() => ({ // Mock app context -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContext) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) // Mock provider context @@ -545,9 +525,9 @@ describe('AppCard', () => { mockUserCanAccessApp.isLoading = false mockDeleteMutationPending = false mockToggleStarMutationPending = false - mockAppContext.isCurrentWorkspaceEditor = true - mockAppContext.userProfile = { id: 'user-1' } - mockAppContext.workspacePermissionKeys = ['app.create_and_management'] + mockConsoleState.isCurrentWorkspaceEditor = true + mockConsoleState.userProfile = { id: 'user-1' } + mockConsoleState.workspacePermissionKeys = ['app.create_and_management'] }) describe('Rendering', () => { @@ -696,9 +676,9 @@ describe('AppCard', () => { }) it('should allow app edit permission to bind tags without workspace tag management permission', () => { - mockAppContext.isCurrentWorkspaceEditor = false - mockAppContext.workspacePermissionKeys = [] - mockAppContext.userProfile = { id: 'user-2' } + mockConsoleState.isCurrentWorkspaceEditor = false + mockConsoleState.workspacePermissionKeys = [] + mockConsoleState.userProfile = { id: 'user-2' } const editableApp = createMockApp({ maintainer: 'user-1', tags: [{ id: 'tag1', name: 'Tag 1', type: 'app' as const, binding_count: '' }], @@ -714,9 +694,9 @@ describe('AppCard', () => { }) it('should allow workspace app tag management permission to bind tags without app edit permission', () => { - mockAppContext.isCurrentWorkspaceEditor = false - mockAppContext.workspacePermissionKeys = ['app.tag.manage'] - mockAppContext.userProfile = { id: 'user-2' } + mockConsoleState.isCurrentWorkspaceEditor = false + mockConsoleState.workspacePermissionKeys = ['app.tag.manage'] + mockConsoleState.userProfile = { id: 'user-2' } const tagManageApp = createMockApp({ maintainer: 'user-1', tags: [{ id: 'tag1', name: 'Tag 1', type: 'app' as const, binding_count: '' }], @@ -732,9 +712,9 @@ describe('AppCard', () => { }) it('should render existing app tags as readonly without app edit or workspace tag management permission', () => { - mockAppContext.isCurrentWorkspaceEditor = false - mockAppContext.workspacePermissionKeys = [] - mockAppContext.userProfile = { id: 'user-2' } + mockConsoleState.isCurrentWorkspaceEditor = false + mockConsoleState.workspacePermissionKeys = [] + mockConsoleState.userProfile = { id: 'user-2' } const readonlyApp = createMockApp({ maintainer: 'user-1', tags: [{ id: 'tag1', name: 'Tag 1', type: 'app' as const, binding_count: '' }], @@ -948,7 +928,7 @@ describe('AppCard', () => { }) it('should show switch option when user can edit app without app creation permission', async () => { - mockAppContext.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] const editableChatApp = createMockApp({ created_by: 'another-user', maintainer: 'another-user', diff --git a/web/app/components/apps/__tests__/creators-filter.spec.tsx b/web/app/components/apps/__tests__/creators-filter.spec.tsx index 455d9566fbc..4b5db4ac8c5 100644 --- a/web/app/components/apps/__tests__/creators-filter.spec.tsx +++ b/web/app/components/apps/__tests__/creators-filter.spec.tsx @@ -1,50 +1,15 @@ -import { fireEvent, render, screen, within } from '@testing-library/react' +import { fireEvent, screen, within } from '@testing-library/react' +import { render } from '@/test/console/render' import CreatorsFilter from '../creators-filter' const mockOnChange = vi.hoisted(() => vi.fn()) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'member-2' }, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'member-2' }, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'member-2' }, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'member-2' }, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'member-2' }, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/use-common', () => ({ useMembers: () => ({ diff --git a/web/app/components/apps/__tests__/index.spec.tsx b/web/app/components/apps/__tests__/index.spec.tsx index fae42caeefb..c39835ba4c3 100644 --- a/web/app/components/apps/__tests__/index.spec.tsx +++ b/web/app/components/apps/__tests__/index.spec.tsx @@ -2,12 +2,13 @@ import type { ReactNode } from 'react' import type { App } from '@/models/explore' import type { TryAppSelection } from '@/types/try-app' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import { useContextSelector } from 'use-context-selector' import AppListContext from '@/context/app-list-context' import { fetchAppDetail } from '@/service/explore' +import { render } from '@/test/console/render' import { AppModeEnum } from '@/types/app' import Apps from '../index' @@ -71,48 +72,12 @@ vi.mock('@/app/education-apply/hooks', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/hooks/use-import-dsl', () => ({ useImportDSL: () => ({ diff --git a/web/app/components/apps/__tests__/list.spec.tsx b/web/app/components/apps/__tests__/list.spec.tsx index ff0f140e54a..4886e4e1ece 100644 --- a/web/app/components/apps/__tests__/list.spec.tsx +++ b/web/app/components/apps/__tests__/list.spec.tsx @@ -4,7 +4,7 @@ import type { TryAppSelection } from '@/types/try-app' import { act, fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' -import { createSystemFeaturesWrapper } from '@/__tests__/utils/mock-system-features' +import { createConsoleQueryWrapper } from '@/test/console/query-data' import { renderWithNuqs } from '@/test/nuqs-testing' import { AppModeEnum } from '@/types/app' import List from '../list' @@ -104,53 +104,20 @@ vi.mock('@/service/client', () => ({ const mockIsCurrentWorkspaceDatasetOperator = vi.fn(() => false) let mockWorkspacePermissionKeys = ['app.create_and_management'] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'creator-1' }, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'creator-1' }, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'creator-1' }, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'creator-1' }, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'creator-1' }, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) const mockOnPlanInfoChanged = vi.fn() vi.mock('@/context/provider-context', () => ({ @@ -527,13 +494,13 @@ type RenderListOptions = { const renderList = (searchParams = '', options: RenderListOptions = {}) => { mockSearchParams = new URLSearchParams(searchParams) - const { wrapper: SystemFeaturesWrapper } = createSystemFeaturesWrapper({ + const { wrapper: ConsoleQueryWrapper } = createConsoleQueryWrapper({ systemFeatures: { branding: { enabled: false }, ...options.systemFeatures }, }) return renderWithNuqs( - + - , + , { searchParams }, ) } diff --git a/web/app/components/base/chat/chat-with-history/__tests__/header-in-mobile.spec.tsx b/web/app/components/base/chat/chat-with-history/__tests__/header-in-mobile.spec.tsx index 98d08ca4373..bf6a1726d30 100644 --- a/web/app/components/base/chat/chat-with-history/__tests__/header-in-mobile.spec.tsx +++ b/web/app/components/base/chat/chat-with-history/__tests__/header-in-mobile.spec.tsx @@ -4,8 +4,8 @@ import type { ChatWithHistoryContextValue } from '../context' import type { AppData, AppMeta } from '@/models/share' import { fireEvent, screen, waitFor } from '@testing-library/react' import * as ReactI18next from 'react-i18next' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { withSelectorKey } from '@/test/i18n-mock' import { useChatWithHistoryContext } from '../context' import HeaderInMobile from '../header-in-mobile' diff --git a/web/app/components/base/chat/chat-with-history/sidebar/__tests__/index.spec.tsx b/web/app/components/base/chat/chat-with-history/sidebar/__tests__/index.spec.tsx index e75a5e838fc..d4f909963e1 100644 --- a/web/app/components/base/chat/chat-with-history/sidebar/__tests__/index.spec.tsx +++ b/web/app/components/base/chat/chat-with-history/sidebar/__tests__/index.spec.tsx @@ -4,8 +4,8 @@ import { screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import * as ReactI18next from 'react-i18next' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { expectLoadingButton } from '@/test/button' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { withSelectorKey } from '@/test/i18n-mock' import { useChatWithHistoryContext } from '../../context' import Sidebar from '../index' @@ -16,7 +16,7 @@ let mockBranding: { enabled: boolean; workspace_logo: string } = { workspace_logo: '', } const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { branding: { ...mockBranding } }, }) diff --git a/web/app/components/base/chat/chat/answer/human-input-content/__tests__/tips.spec.tsx b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/tips.spec.tsx index 74a4a4e3dd2..58171322548 100644 --- a/web/app/components/base/chat/chat/answer/human-input-content/__tests__/tips.spec.tsx +++ b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/tips.spec.tsx @@ -1,50 +1,23 @@ -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import Tips from '../tips' -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { email: 'test@example.com', }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) describe('Tips', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContextState.userProfile.email = 'test@example.com' + mockConsoleState.userProfile.email = 'test@example.com' }) it('should render email tip in normal mode', () => { diff --git a/web/app/components/base/chat/chat/answer/human-input-content/__tests__/unsubmitted.spec.tsx b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/unsubmitted.spec.tsx index 5fe1c4c56ce..eefd01262d8 100644 --- a/web/app/components/base/chat/chat/answer/human-input-content/__tests__/unsubmitted.spec.tsx +++ b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/unsubmitted.spec.tsx @@ -1,9 +1,10 @@ import type { InputVarType } from '@/app/components/workflow/types' import type { HumanInputFormData } from '@/types/workflow' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, expect, it, vi } from 'vitest' import { UserActionButtonType } from '@/app/components/workflow/nodes/human-input/types' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { UnsubmittedHumanInputContent } from '../unsubmitted' describe('UnsubmittedHumanInputContent Integration', () => { diff --git a/web/app/components/base/chat/embedded-chatbot/__tests__/index.spec.tsx b/web/app/components/base/chat/embedded-chatbot/__tests__/index.spec.tsx index 073998c2a53..2d966c8d192 100644 --- a/web/app/components/base/chat/embedded-chatbot/__tests__/index.spec.tsx +++ b/web/app/components/base/chat/embedded-chatbot/__tests__/index.spec.tsx @@ -3,14 +3,14 @@ import type { ChatConfig } from '../../types' import type { AppData, AppMeta, ConversationItem } from '@/models/share' import { screen } from '@testing-library/react' import { vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { useEmbeddedChatbot } from '../hooks' import EmbeddedChatbot from '../index' let mockBrandingWorkspaceLogo = '' const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { branding: { enabled: true, workspace_logo: mockBrandingWorkspaceLogo }, }, diff --git a/web/app/components/base/chat/embedded-chatbot/header/__tests__/index.spec.tsx b/web/app/components/base/chat/embedded-chatbot/header/__tests__/index.spec.tsx index c9a1857e751..176dbf08e1b 100644 --- a/web/app/components/base/chat/embedded-chatbot/header/__tests__/index.spec.tsx +++ b/web/app/components/base/chat/embedded-chatbot/header/__tests__/index.spec.tsx @@ -3,13 +3,13 @@ import type { EmbeddedChatbotContextValue } from '../../context' import type { AppData } from '@/models/share' import { act, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { useEmbeddedChatbotContext } from '../../context' import Header from '../index' let mockBranding = { enabled: true, workspace_logo: '' } const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { branding: { ...mockBranding } }, }) diff --git a/web/app/components/billing/billing-page/__tests__/index.spec.tsx b/web/app/components/billing/billing-page/__tests__/index.spec.tsx index 0fef57b4888..e9f30459d73 100644 --- a/web/app/components/billing/billing-page/__tests__/index.spec.tsx +++ b/web/app/components/billing/billing-page/__tests__/index.spec.tsx @@ -1,4 +1,5 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { render } from '@/test/console/render' import Billing from '../index' let currentBillingUrl: string | null = 'https://billing' @@ -34,47 +35,20 @@ vi.mock('@/hooks/use-async-window-open', () => ({ useAsyncWindowOpen: () => openAsyncWindowMock, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ isCurrentWorkspaceManager: isManager, workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ isCurrentWorkspaceManager: isManager, workspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: isManager, - workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: isManager, - workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: isManager, - workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/provider-context', () => ({ useProviderContext: () => ({ diff --git a/web/app/components/billing/pricing/__tests__/dialog.spec.tsx b/web/app/components/billing/pricing/__tests__/dialog.spec.tsx index b79579dcc9a..b26fd54ad74 100644 --- a/web/app/components/billing/pricing/__tests__/dialog.spec.tsx +++ b/web/app/components/billing/pricing/__tests__/dialog.spec.tsx @@ -1,9 +1,9 @@ import type { ReactNode } from 'react' import type { Mock } from 'vitest' import type { UsagePlanInfo } from '../../type' -import { render } from '@testing-library/react' import { useGetPricingPageLanguage } from '@/context/i18n' import { useProviderContext } from '@/context/provider-context' +import { render } from '@/test/console/render' import { Plan } from '../../type' import Pricing from '../index' @@ -14,7 +14,7 @@ type DialogProps = { } let latestOnOpenChange: DialogProps['onOpenChange'] -let mockAppCtx: Record = {} +let mockConsoleState: Record = {} vi.mock('@langgenius/dify-ui/dialog', () => ({ Dialog: ({ children, onOpenChange }: DialogProps) => { @@ -46,31 +46,13 @@ vi.mock('../footer', () => ({ default: () =>
footer
, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppCtx) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) vi.mock('@/context/provider-context', () => ({ @@ -95,7 +77,7 @@ describe('Pricing dialog lifecycle', () => { beforeEach(() => { vi.clearAllMocks() latestOnOpenChange = undefined - mockAppCtx = { + mockConsoleState = { isCurrentWorkspaceManager: true, workspacePermissionKeys: ['billing.manage'], } diff --git a/web/app/components/custom/custom-page/__tests__/index.spec.tsx b/web/app/components/custom/custom-page/__tests__/index.spec.tsx index bcd54ed2180..6e375ab2aed 100644 --- a/web/app/components/custom/custom-page/__tests__/index.spec.tsx +++ b/web/app/components/custom/custom-page/__tests__/index.spec.tsx @@ -1,15 +1,13 @@ import type { ReactElement } from 'react' -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createMockProviderContextValue } from '@/__mocks__/provider-context' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { contactSalesUrl, defaultPlan } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' -import { initialLangGeniusVersionInfo, initialWorkspaceInfo } from '@/context/app-context-defaults' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' +import { renderWithConsoleQuery } from '@/test/console/query-data' import CustomPage from '../index' vi.mock('@/config', async (importOriginal) => { @@ -21,7 +19,7 @@ vi.mock('@/config', async (importOriginal) => { }) const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { branding: { enabled: true, @@ -42,8 +40,6 @@ const { mockToast } = vi.hoisted(() => { }) return { mockToast } }) -const mockUseAppContext = vi.hoisted(() => vi.fn()) - vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(), })) @@ -57,15 +53,6 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ const mockUseProviderContext = vi.mocked(useProviderContext) const mockUseModalContext = vi.mocked(useModalContext) -const testUserProfile = { - id: '', - name: '', - email: '', - avatar: '', - avatar_url: '', - is_password_set: false, -} - const createProviderContext = ({ enableBilling = false, planType = Plan.professional, @@ -82,26 +69,6 @@ const createProviderContext = ({ }) } -const createAppContextValue = (): AppContextStateMockState => ({ - userProfile: testUserProfile, - mutateUserProfile: vi.fn(), - currentWorkspace: { - ...initialWorkspaceInfo, - custom_config: { - replace_webapp_logo: 'https://example.com/replace.png', - remove_webapp_brand: false, - }, - }, - isCurrentWorkspaceManager: true, - isCurrentWorkspaceOwner: false, - isCurrentWorkspaceEditor: false, - isCurrentWorkspaceDatasetOperator: false, - mutateCurrentWorkspace: vi.fn(), - langGeniusVersionInfo: initialLangGeniusVersionInfo, - isLoadingCurrentWorkspace: false, - workspacePermissionKeys: [], -}) - describe('CustomPage', () => { const setShowPricingModal = vi.fn() @@ -112,7 +79,6 @@ describe('CustomPage', () => { mockUseModalContext.mockReturnValue({ setShowPricingModal, } as unknown as ReturnType) - mockUseAppContext.mockReturnValue(createAppContextValue()) }) // Integration coverage for the page and its child custom brand section. diff --git a/web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx b/web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx index f31bcb0004a..2ee2c070bcf 100644 --- a/web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx +++ b/web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx @@ -1,22 +1,22 @@ import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import type { ChangeEvent } from 'react' -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { ICurrentWorkspace } from '@/models/common' +import type { ConsoleStateFixture } from '@/test/console/state-fixture' import { act } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createMockProviderContextValue } from '@/__mocks__/provider-context' -import { renderHookWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/image-uploader/utils' import { defaultPlan } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' import { initialLangGeniusVersionInfo, initialWorkspaceInfo } from '@/context/app-context-defaults' import { useProviderContext } from '@/context/provider-context' import { updateCurrentWorkspace } from '@/service/common' +import { renderHookWithConsoleQuery } from '@/test/console/query-data' import useWebAppBrand from '../use-web-app-brand' let currentBrandingOverrides: Partial = {} const renderHook = (callback: (props: Props) => Result) => - renderHookWithSystemFeatures(callback, { + renderHookWithConsoleQuery(callback, { systemFeatures: { branding: { enabled: true, @@ -39,10 +39,10 @@ const { mockNotify, mockToast } = vi.hoisted(() => { }) return { mockNotify, mockToast } }) -const appContextStateRef = vi.hoisted(() => ({ - value: undefined as AppContextStateMockState | undefined, +const consoleStateRef = vi.hoisted(() => ({ + value: undefined as ConsoleStateFixture | undefined, })) -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) vi.mock('@langgenius/dify-ui/toast', () => ({ toast: mockToast, @@ -50,52 +50,35 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ vi.mock('@/service/common', () => ({ updateCurrentWorkspace: vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - ...appContextStateRef.value, - refreshCurrentWorkspace: appContextStateRef.value?.mutateCurrentWorkspace, +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ + ...consoleStateRef.value, + refreshCurrentWorkspace: consoleStateRef.value?.refreshCurrentWorkspace, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - ...appContextStateRef.value, - refreshCurrentWorkspace: appContextStateRef.value?.mutateCurrentWorkspace, +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + ...consoleStateRef.value, + refreshCurrentWorkspace: consoleStateRef.value?.refreshCurrentWorkspace, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - ...appContextStateRef.value, - refreshCurrentWorkspace: appContextStateRef.value?.mutateCurrentWorkspace, +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ + ...consoleStateRef.value, + refreshCurrentWorkspace: consoleStateRef.value?.refreshCurrentWorkspace, })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - ...appContextStateRef.value, - refreshCurrentWorkspace: appContextStateRef.value?.mutateCurrentWorkspace, +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => ({ + ...consoleStateRef.value, + refreshCurrentWorkspace: consoleStateRef.value?.refreshCurrentWorkspace, })) }) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - ...appContextStateRef.value, - refreshCurrentWorkspace: appContextStateRef.value?.mutateCurrentWorkspace, - })) -}) -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(), })) @@ -134,11 +117,9 @@ const createProviderContext = ({ }) } -const createAppContextValue = ( - overrides: Partial = {}, -): AppContextStateMockState => { +const createConsoleState = (overrides: Partial = {}): ConsoleStateFixture => { const { currentWorkspace: currentWorkspaceOverride, ...restOverrides } = overrides - const workspaceOverrides: Partial = + const workspaceOverrides: Partial = currentWorkspaceOverride ?? {} const currentWorkspace = { ...initialWorkspaceInfo, @@ -152,13 +133,13 @@ const createAppContextValue = ( return { userProfile: testUserProfile, - mutateUserProfile: vi.fn(), + refreshUserProfile: vi.fn(), isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, isCurrentWorkspaceEditor: false, isCurrentWorkspaceDatasetOperator: false, workspacePermissionKeys: ['customization.manage'], - mutateCurrentWorkspace: vi.fn(), + refreshCurrentWorkspace: vi.fn(), langGeniusVersionInfo: initialLangGeniusVersionInfo, isLoadingCurrentWorkspace: false, ...restOverrides, @@ -167,22 +148,20 @@ const createAppContextValue = ( } describe('useWebAppBrand', () => { - let appContextValue: AppContextStateMockState - const setAppContextValue = (nextValue: AppContextStateMockState) => { - appContextValue = nextValue - appContextStateRef.value = nextValue + let consoleState: ConsoleStateFixture + const setConsoleState = (nextValue: ConsoleStateFixture) => { + consoleState = nextValue + consoleStateRef.value = nextValue } beforeEach(() => { vi.clearAllMocks() - setAppContextValue(createAppContextValue()) + setConsoleState(createConsoleState()) currentBrandingOverrides = {} - mockUpdateCurrentWorkspace.mockResolvedValue( - appContextValue.currentWorkspace as ICurrentWorkspace, - ) - mockUseAppContext.mockImplementation(() => appContextValue) + mockUpdateCurrentWorkspace.mockResolvedValue(consoleState.currentWorkspace as ICurrentWorkspace) + mockConsoleStateReader.mockImplementation(() => consoleState) mockUseProviderContext.mockReturnValue(createProviderContext()) mockGetImageUploadErrorMessage.mockReturnValue('upload error') }) @@ -200,8 +179,8 @@ describe('useWebAppBrand', () => { }) it('should disable uploads when customization management permission is missing', () => { - setAppContextValue( - createAppContextValue({ + setConsoleState( + createConsoleState({ workspacePermissionKeys: [], isCurrentWorkspaceManager: true, }), @@ -214,8 +193,8 @@ describe('useWebAppBrand', () => { }) it('should allow uploads for non-manager users with customization management permission', () => { - setAppContextValue( - createAppContextValue({ + setConsoleState( + createConsoleState({ workspacePermissionKeys: ['customization.manage'], isCurrentWorkspaceManager: false, }), @@ -234,8 +213,8 @@ describe('useWebAppBrand', () => { planType: Plan.sandbox, }), ) - setAppContextValue( - createAppContextValue({ + setConsoleState( + createConsoleState({ currentWorkspace: { ...initialWorkspaceInfo, custom_config: { @@ -262,8 +241,8 @@ describe('useWebAppBrand', () => { }) it('should fall back to an empty custom logo when custom config is missing', () => { - setAppContextValue({ - ...createAppContextValue(), + setConsoleState({ + ...createConsoleState(), currentWorkspace: { ...initialWorkspaceInfo, }, @@ -370,9 +349,9 @@ describe('useWebAppBrand', () => { it('should persist the selected logo and reset transient state on apply', async () => { const mutateCurrentWorkspace = vi.fn() - setAppContextValue( - createAppContextValue({ - mutateCurrentWorkspace, + setConsoleState( + createConsoleState({ + refreshCurrentWorkspace: mutateCurrentWorkspace, }), ) mockImageUpload.mockImplementation(({ onSuccessCallback }) => { @@ -409,9 +388,9 @@ describe('useWebAppBrand', () => { it('should restore the default branding configuration', async () => { const mutateCurrentWorkspace = vi.fn() - setAppContextValue( - createAppContextValue({ - mutateCurrentWorkspace, + setConsoleState( + createConsoleState({ + refreshCurrentWorkspace: mutateCurrentWorkspace, }), ) @@ -433,9 +412,9 @@ describe('useWebAppBrand', () => { it('should persist brand removal changes', async () => { const mutateCurrentWorkspace = vi.fn() - setAppContextValue( - createAppContextValue({ - mutateCurrentWorkspace, + setConsoleState( + createConsoleState({ + refreshCurrentWorkspace: mutateCurrentWorkspace, }), ) diff --git a/web/app/components/datasets/__tests__/mock-dataset-access.ts b/web/app/components/datasets/__tests__/mock-dataset-access.ts deleted file mode 100644 index dd5208b5439..00000000000 --- a/web/app/components/datasets/__tests__/mock-dataset-access.ts +++ /dev/null @@ -1,136 +0,0 @@ -const DATASET_ACCESS_ATOM_KIND = Symbol('dataset-access-atom-kind') - -type DatasetAccessMockState = { - userProfile?: { - id?: string - name?: string - email?: string - avatar?: string - avatar_url?: string - is_password_set?: boolean - } | null - currentWorkspace?: { - id?: string - } | null - isCurrentWorkspaceOwner?: boolean - isLoadingCurrentWorkspace?: boolean - isLoadingWorkspacePermissionKeys?: boolean - workspacePermissionKeys?: string[] -} - -type DatasetAccessMockOptions = { - isRbacEnabled?: boolean -} - -type DatasetAccessAtomKind = - | 'userProfile' - | 'userProfileId' - | 'currentWorkspaceId' - | 'isCurrentWorkspaceOwner' - | 'workspacePermissionKeys' - | 'currentWorkspaceLoading' - | 'workspacePermissionKeysLoading' - | 'datasetRbacEnabled' - -type DatasetAccessMockAtom = { - [DATASET_ACCESS_ATOM_KIND]: DatasetAccessAtomKind -} - -type DatasetAccessMockRegistry = { - getState: () => DatasetAccessMockState - getOptions: () => DatasetAccessMockOptions -} - -const defaultUserProfile = { - id: 'user-1', - name: 'User', - email: 'user@example.com', - avatar: '', - avatar_url: '', - is_password_set: true, -} - -let datasetAccessMockRegistry: DatasetAccessMockRegistry | undefined - -const createMockAtom = (kind: DatasetAccessAtomKind): DatasetAccessMockAtom => ({ - [DATASET_ACCESS_ATOM_KIND]: kind, -}) - -const isDatasetAccessMockAtom = (atom: unknown): atom is DatasetAccessMockAtom => { - return typeof atom === 'object' && atom !== null && DATASET_ACCESS_ATOM_KIND in atom -} - -const getUserProfile = (state: DatasetAccessMockState) => ({ - ...defaultUserProfile, - ...state.userProfile, -}) - -const getWorkspacePermissionKeys = (state: DatasetAccessMockState) => - state.workspacePermissionKeys ?? [] - -export const createDatasetAccessAtomMock = async ( - importOriginal: () => Promise, - getState: () => DatasetAccessMockState, - getOptions: () => DatasetAccessMockOptions = () => ({}), -) => { - const actual = await importOriginal() - datasetAccessMockRegistry = { - getState, - getOptions, - } - - return { - ...actual, - userProfileAtom: createMockAtom('userProfile'), - userProfileIdAtom: createMockAtom('userProfileId'), - currentWorkspaceIdAtom: createMockAtom('currentWorkspaceId'), - isCurrentWorkspaceOwnerAtom: createMockAtom('isCurrentWorkspaceOwner'), - workspacePermissionKeysAtom: createMockAtom('workspacePermissionKeys'), - currentWorkspaceLoadingAtom: createMockAtom('currentWorkspaceLoading'), - workspacePermissionKeysLoadingAtom: createMockAtom('workspacePermissionKeysLoading'), - datasetRbacEnabledAtom: createMockAtom('datasetRbacEnabled'), - } -} - -export const createDatasetAccessJotaiMock = async (importOriginal: () => Promise) => { - const actual = await importOriginal() - - return { - ...actual, - useAtomValue: (atom: unknown) => { - if (!isDatasetAccessMockAtom(atom)) - return actual.useAtomValue(atom as Parameters[0]) - - if (!datasetAccessMockRegistry) throw new Error('Dataset access atom mock is not initialized') - - const state = datasetAccessMockRegistry.getState() - const options = datasetAccessMockRegistry.getOptions() - const userProfile = getUserProfile(state) - const workspacePermissionKeys = getWorkspacePermissionKeys(state) - - if (atom[DATASET_ACCESS_ATOM_KIND] === 'userProfile') return userProfile - - if (atom[DATASET_ACCESS_ATOM_KIND] === 'userProfileId') return userProfile.id - - if (atom[DATASET_ACCESS_ATOM_KIND] === 'currentWorkspaceId') - return state.currentWorkspace?.id ?? 'workspace-1' - - if (atom[DATASET_ACCESS_ATOM_KIND] === 'isCurrentWorkspaceOwner') - return state.isCurrentWorkspaceOwner ?? false - - if (atom[DATASET_ACCESS_ATOM_KIND] === 'workspacePermissionKeys') - return workspacePermissionKeys - - if (atom[DATASET_ACCESS_ATOM_KIND] === 'currentWorkspaceLoading') - return state.isLoadingCurrentWorkspace ?? false - - if (atom[DATASET_ACCESS_ATOM_KIND] === 'workspacePermissionKeysLoading') - return state.isLoadingWorkspacePermissionKeys ?? false - - if (atom[DATASET_ACCESS_ATOM_KIND] === 'datasetRbacEnabled') - return options.isRbacEnabled ?? true - - throw new Error(`Unsupported dataset access atom: ${atom[DATASET_ACCESS_ATOM_KIND]}`) - }, - } -} diff --git a/web/app/components/datasets/access-config/__tests__/index.spec.tsx b/web/app/components/datasets/access-config/__tests__/index.spec.tsx index 580718e0c6d..307059e9bd2 100644 --- a/web/app/components/datasets/access-config/__tests__/index.spec.tsx +++ b/web/app/components/datasets/access-config/__tests__/index.spec.tsx @@ -1,10 +1,10 @@ import type { AccessRulesEditorProps } from '@/app/components/access-rules-editor' import { screen } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useDatasetAccessRules, useDatasetUserAccessSettings, } from '@/service/access-control/use-dataset-access-config' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { DatasetACLPermission } from '@/utils/permission' import DatasetAccessConfigPage from '../index' @@ -23,15 +23,15 @@ const mockDatasetDetail = vi.hoisted(() => ({ dataset: undefined as { maintainer?: string | null; permission_keys?: string[] } | undefined, })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], })) let mockIsRbacEnabled = true -const render = (ui: Parameters[0]) => - renderWithSystemFeatures(ui, { +const render = (ui: Parameters[0]) => + renderWithConsoleQuery(ui, { systemFeatures: { rbac_enabled: mockIsRbacEnabled, }, @@ -82,65 +82,30 @@ vi.mock('@/context/dataset-detail', () => ({ ), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ + return createSystemFeaturesStateModuleMock(() => ({ + ...(() => mockConsoleState)(), + datasetRbacEnabled: (() => ({ isRbacEnabled: mockIsRbacEnabled, - }), - ) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + }))().isRbacEnabled, + })) }) vi.mock('@/app/components/access-rules-editor', () => ({ @@ -150,13 +115,6 @@ vi.mock('@/app/components/access-rules-editor', () => ({ }, })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - describe('DatasetAccessConfigPage', () => { beforeEach(() => { vi.clearAllMocks() @@ -169,8 +127,8 @@ describe('DatasetAccessConfigPage', () => { maintainer: 'maintainer-1', permission_keys: [DatasetACLPermission.AccessConfig], } - mockAppContextState.userProfile = { id: 'user-1' } - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.userProfile = { id: 'user-1' } + mockConsoleState.workspacePermissionKeys = [] mockIsRbacEnabled = true mockAccessRulesEditor.props = null }) diff --git a/web/app/components/datasets/common/image-previewer/__tests__/index.spec.tsx b/web/app/components/datasets/common/image-previewer/__tests__/index.spec.tsx index f6f3041c01d..ce76a269ad2 100644 --- a/web/app/components/datasets/common/image-previewer/__tests__/index.spec.tsx +++ b/web/app/components/datasets/common/image-previewer/__tests__/index.spec.tsx @@ -1,495 +1,158 @@ -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import ImagePreviewer from '../index' -// Mock fetch -const mockFetch = vi.fn() -globalThis.fetch = mockFetch - -// Mock URL methods +const mockFetch = vi.fn() const mockRevokeObjectURL = vi.fn() const mockCreateObjectURL = vi.fn(() => 'blob:mock-url') -globalThis.URL.revokeObjectURL = mockRevokeObjectURL -globalThis.URL.createObjectURL = mockCreateObjectURL class MockImage { + naturalWidth = 800 + naturalHeight = 600 onload: (() => void) | null = null onerror: (() => void) | null = null - _src = '' + private source = '' get src() { - return this._src + return this.source } set src(value: string) { - this._src = value - // Trigger onload after a microtask - setTimeout(() => { - if (this.onload) this.onload() - }, 0) + this.source = value + queueMicrotask(() => this.onload?.()) } - - naturalWidth = 800 - naturalHeight = 600 } -;(globalThis as unknown as { Image: typeof MockImage }).Image = MockImage -const createMockImages = () => [ +const images = [ { url: 'https://example.com/image1.png', name: 'image1.png', size: 1024 }, { url: 'https://example.com/image2.png', name: 'image2.png', size: 2048 }, { url: 'https://example.com/image3.png', name: 'image3.png', size: 3072 }, ] +const successfulResponse = () => new Response(new Blob(['test'], { type: 'image/png' })) + +const getPreviewButtons = () => { + const [closeButton, previousButton, nextButton] = screen.getAllByRole('button') + + expect(closeButton).toBeInTheDocument() + expect(previousButton).toBeInTheDocument() + expect(nextButton).toBeInTheDocument() + + return { closeButton: closeButton!, previousButton: previousButton!, nextButton: nextButton! } +} + describe('ImagePreviewer', () => { beforeEach(() => { - vi.clearAllMocks() - // Default successful fetch mock - mockFetch.mockResolvedValue({ - ok: true, - blob: () => Promise.resolve(new Blob(['test'], { type: 'image/png' })), - }) + mockFetch.mockReset().mockImplementation(async () => successfulResponse()) + mockCreateObjectURL.mockClear() + mockRevokeObjectURL.mockClear() + vi.stubGlobal('fetch', mockFetch) + vi.stubGlobal('Image', MockImage) + vi.spyOn(URL, 'createObjectURL').mockImplementation(mockCreateObjectURL) + vi.spyOn(URL, 'revokeObjectURL').mockImplementation(mockRevokeObjectURL) }) afterEach(() => { + vi.unstubAllGlobals() vi.restoreAllMocks() }) - describe('Rendering', () => { - it('should render close button', async () => { - const onClose = vi.fn() - const images = createMockImages() + it('shows the preview and loading state while images are loading', () => { + mockFetch.mockImplementation(() => new Promise(() => {})) - await act(async () => { - render() - }) + render() - // Esc text should be visible - // Esc text should be visible - expect(screen.getByText('Esc'))!.toBeInTheDocument() - }) - - it('should show loading state initially', async () => { - const onClose = vi.fn() - const images = createMockImages() - - // Delay fetch to see loading state - mockFetch.mockImplementation(() => new Promise(() => {})) - - await act(async () => { - render() - }) - - // Loading component should be visible - // Loading component should be visible - expect(document.body.querySelector('.image-previewer'))!.toBeInTheDocument() - }) + expect(screen.getByRole('dialog')).toBeInTheDocument() + expect(screen.getByRole('status')).toBeInTheDocument() + expect(screen.getByText('Esc')).toBeInTheDocument() }) - describe('Props', () => { - it('should start at initialIndex', async () => { - const onClose = vi.fn() - const images = createMockImages() + it('loads every image and displays the first image metadata by default', async () => { + render() - await act(async () => { - render() - }) - - await waitFor(() => { - // Should start at second image - // Should start at second image - expect(screen.getByText('image2.png'))!.toBeInTheDocument() - }) - }) - - it('should default initialIndex to 0', async () => { - const onClose = vi.fn() - const images = createMockImages() - - await act(async () => { - render() - }) - - await waitFor(() => { - expect(screen.getByText('image1.png'))!.toBeInTheDocument() - }) - }) + expect(await screen.findByRole('img', { name: 'image1.png' })).toHaveAttribute( + 'src', + 'blob:mock-url', + ) + expect(screen.getByText(/800.*600/)).toBeInTheDocument() + expect(screen.getByText('1.00 KB')).toBeInTheDocument() + expect(mockFetch).toHaveBeenCalledTimes(3) + expect(mockFetch.mock.calls.map(([url]) => url)).toEqual(images.map(({ url }) => url)) }) - describe('User Interactions', () => { - it('should call onClose when close button is clicked', async () => { - const onClose = vi.fn() - const images = createMockImages() + it('starts from the requested image', async () => { + render() - await act(async () => { - render() - }) - - // Find and click close button (the one with RiCloseLine icon) - const closeButton = document.querySelector('.absolute.right-6 button') - if (closeButton) { - fireEvent.click(closeButton) - expect(onClose).toHaveBeenCalledTimes(1) - } - }) - - it('should navigate to next image when next button is clicked', async () => { - const onClose = vi.fn() - const images = createMockImages() - - await act(async () => { - render() - }) - - await waitFor(() => { - expect(screen.getByText('image1.png'))!.toBeInTheDocument() - }) - - // Find and click next button (right arrow) - const buttons = document.querySelectorAll('button') - const nextButton = Array.from(buttons).find((btn) => btn.className.includes('right-8')) - - if (nextButton) { - await act(async () => { - fireEvent.click(nextButton) - }) - - await waitFor(() => { - expect(screen.getByText('image2.png'))!.toBeInTheDocument() - }) - } - }) - - it('should navigate to previous image when prev button is clicked', async () => { - const onClose = vi.fn() - const images = createMockImages() - - await act(async () => { - render() - }) - - await waitFor(() => { - expect(screen.getByText('image2.png'))!.toBeInTheDocument() - }) - - // Find and click prev button (left arrow) - const buttons = document.querySelectorAll('button') - const prevButton = Array.from(buttons).find((btn) => btn.className.includes('left-8')) - - if (prevButton) { - await act(async () => { - fireEvent.click(prevButton) - }) - - await waitFor(() => { - expect(screen.getByText('image1.png'))!.toBeInTheDocument() - }) - } - }) - - it('should disable prev button at first image', async () => { - const onClose = vi.fn() - const images = createMockImages() - - await act(async () => { - render() - }) - - const buttons = document.querySelectorAll('button') - const prevButton = Array.from(buttons).find((btn) => btn.className.includes('left-8')) - - expect(prevButton)!.toBeDisabled() - }) - - it('should disable next button at last image', async () => { - const onClose = vi.fn() - const images = createMockImages() - - await act(async () => { - render() - }) - - const buttons = document.querySelectorAll('button') - const nextButton = Array.from(buttons).find((btn) => btn.className.includes('right-8')) - - expect(nextButton)!.toBeDisabled() - }) + expect(await screen.findByRole('img', { name: 'image2.png' })).toBeInTheDocument() }) - describe('Image Loading', () => { - it('should fetch images on mount', async () => { - const onClose = vi.fn() - const images = createMockImages() + it('navigates between images and disables navigation at the boundaries', async () => { + const user = userEvent.setup() + render() + await screen.findByRole('img', { name: 'image1.png' }) + const { previousButton, nextButton } = getPreviewButtons() - await act(async () => { - render() - }) + expect(previousButton).toBeDisabled() + expect(nextButton).toBeEnabled() - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - }) - }) + await user.click(nextButton) + expect(screen.getByRole('img', { name: 'image2.png' })).toBeInTheDocument() + expect(previousButton).toBeEnabled() - it('should show error state when fetch fails', async () => { - const onClose = vi.fn() - const images = createMockImages() + await user.click(nextButton) + expect(screen.getByRole('img', { name: 'image3.png' })).toBeInTheDocument() + expect(nextButton).toBeDisabled() - mockFetch.mockRejectedValue(new Error('Network error')) - - await act(async () => { - render() - }) - - await waitFor(() => { - expect(screen.getByText(/Failed to load image/))!.toBeInTheDocument() - }) - }) - - it('should show retry button on error', async () => { - const onClose = vi.fn() - const images = createMockImages() - - mockFetch.mockRejectedValue(new Error('Network error')) - - await act(async () => { - render() - }) - - await waitFor(() => { - // Retry button should be visible - const retryButton = document.querySelector('button.rounded-full') - expect(retryButton)!.toBeInTheDocument() - }) - }) + await user.click(previousButton) + expect(screen.getByRole('img', { name: 'image2.png' })).toBeInTheDocument() }) - describe('Navigation Boundary Cases', () => { - it('should not navigate past first image when prevImage is called at index 0', async () => { - const onClose = vi.fn() - const images = createMockImages() + it('disables both navigation buttons for a single image', async () => { + render() + await screen.findByRole('img', { name: 'image1.png' }) + const { previousButton, nextButton } = getPreviewButtons() - await act(async () => { - render() - }) - - await waitFor(() => { - expect(screen.getByText('image1.png'))!.toBeInTheDocument() - }) - - const buttons = document.querySelectorAll('button') - const prevButton = Array.from(buttons).find((btn) => btn.className.includes('left-8')) - - if (prevButton) { - await act(async () => { - fireEvent.click(prevButton) - fireEvent.click(prevButton) - }) - - // Should still be at first image - await waitFor(() => { - expect(screen.getByText('image1.png'))!.toBeInTheDocument() - }) - } - }) - - it('should not navigate past last image when nextImage is called at last index', async () => { - const onClose = vi.fn() - const images = createMockImages() - - await act(async () => { - render() - }) - - await waitFor(() => { - expect(screen.getByText('image3.png'))!.toBeInTheDocument() - }) - - const buttons = document.querySelectorAll('button') - const nextButton = Array.from(buttons).find((btn) => btn.className.includes('right-8')) - - if (nextButton) { - await act(async () => { - fireEvent.click(nextButton) - fireEvent.click(nextButton) - }) - - // Should still be at last image - await waitFor(() => { - expect(screen.getByText('image3.png'))!.toBeInTheDocument() - }) - } - }) + expect(previousButton).toBeDisabled() + expect(nextButton).toBeDisabled() }) - describe('Retry Functionality', () => { - it('should retry image load when retry button is clicked', async () => { - const onClose = vi.fn() - const images = createMockImages() + it('keeps the preview open on content clicks and closes from the close button', async () => { + const user = userEvent.setup() + const onClose = vi.fn() + render() + await screen.findByRole('img', { name: 'image1.png' }) + const { closeButton } = getPreviewButtons() - // First fail, then succeed - let callCount = 0 - mockFetch.mockImplementation(() => { - callCount++ - if (callCount === 1) { - return Promise.reject(new Error('Network error')) - } - return Promise.resolve({ - ok: true, - blob: () => Promise.resolve(new Blob(['test'], { type: 'image/png' })), - }) - }) + await user.click(screen.getByRole('dialog')) + expect(onClose).not.toHaveBeenCalled() - await act(async () => { - render() - }) - - // Wait for error state - await waitFor(() => { - expect(screen.getByText(/Failed to load image/))!.toBeInTheDocument() - }) - - const retryButton = document.querySelector('button.rounded-full') - if (retryButton) { - await act(async () => { - fireEvent.click(retryButton) - }) - - // Should refetch the image - await waitFor(() => { - expect(mockFetch).toHaveBeenCalledTimes(4) // 3 initial + 1 retry - }) - } - }) - - it('should show retry button and call retryImage when clicked', async () => { - const onClose = vi.fn() - const images = createMockImages() - - mockFetch.mockRejectedValue(new Error('Network error')) - - await act(async () => { - render() - }) - - await waitFor(() => { - expect(screen.getByText(/Failed to load image/))!.toBeInTheDocument() - }) - - // Find and click the retry button (not the nav buttons) - const allButtons = document.querySelectorAll('button') - const retryButton = Array.from(allButtons).find( - (btn) => - btn.className.includes('rounded-full') && - !btn.className.includes('left-8') && - !btn.className.includes('right-8'), - ) - - expect(retryButton)!.toBeInTheDocument() - - if (retryButton) { - mockFetch.mockClear() - mockFetch.mockResolvedValue({ - ok: true, - blob: () => Promise.resolve(new Blob(['test'], { type: 'image/png' })), - }) - - await act(async () => { - fireEvent.click(retryButton) - }) - - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - }) - } - }) + await user.click(closeButton) + expect(onClose).toHaveBeenCalledOnce() }) - describe('Image Cache', () => { - it('should clean up blob URLs on unmount', async () => { - const onClose = vi.fn() - const images = createMockImages() + it('shows an error and retries the failed image', async () => { + const user = userEvent.setup() + mockFetch.mockRejectedValueOnce(new Error('Network error')) + render() - // First render to populate cache - const { unmount } = await act(async () => { - const result = render() - return result - }) + expect(await screen.findByText(/Failed to load image/)).toHaveTextContent(images[0]!.url) + const [, retryButton] = screen.getAllByRole('button') + expect(retryButton).toBeInTheDocument() - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - }) + await user.click(retryButton!) - // Store the call count for verification - const _firstCallCount = mockFetch.mock.calls.length - - unmount() - - // Note: The imageCache is cleared on unmount, so this test verifies - // the cleanup behavior rather than caching across mounts - expect(mockRevokeObjectURL).toHaveBeenCalled() - }) + expect(await screen.findByRole('img', { name: 'image1.png' })).toBeInTheDocument() + expect(mockFetch).toHaveBeenCalledTimes(4) + expect(mockFetch).toHaveBeenLastCalledWith(images[0]!.url) }) - describe('Edge Cases', () => { - it('should handle single image', async () => { - const onClose = vi.fn() - const images = [createMockImages()[0]!] + it('revokes loaded blob URLs on unmount', async () => { + const { unmount } = render() + await screen.findByRole('img', { name: 'image1.png' }) - await act(async () => { - render() - }) + unmount() - // Both navigation buttons should be disabled - const buttons = document.querySelectorAll('button') - const prevButton = Array.from(buttons).find((btn) => btn.className.includes('left-8')) - const nextButton = Array.from(buttons).find((btn) => btn.className.includes('right-8')) - - expect(prevButton)!.toBeDisabled() - expect(nextButton)!.toBeDisabled() - }) - - it('should not close on container click', async () => { - const onClose = vi.fn() - const images = createMockImages() - - await act(async () => { - render() - }) - - const container = document.querySelector('.image-previewer') - if (container) { - fireEvent.click(container) - expect(onClose).not.toHaveBeenCalled() - } - }) - - it('should display image dimensions when loaded', async () => { - const onClose = vi.fn() - const images = createMockImages() - - await act(async () => { - render() - }) - - await waitFor(() => { - // Should display dimensions (800 × 600 from MockImage) - // Should display dimensions (800 × 600 from MockImage) - expect(screen.getByText(/800.*600/))!.toBeInTheDocument() - }) - }) - - it('should display file size', async () => { - const onClose = vi.fn() - const images = createMockImages() - - await act(async () => { - render() - }) - - await waitFor(() => { - // Should display formatted file size - // Should display formatted file size - expect(screen.getByText('image1.png'))!.toBeInTheDocument() - }) - }) + await waitFor(() => expect(mockRevokeObjectURL).toHaveBeenCalledTimes(3)) }) }) diff --git a/web/app/components/datasets/create-from-pipeline/list/__tests__/built-in-pipeline-list.spec.tsx b/web/app/components/datasets/create-from-pipeline/list/__tests__/built-in-pipeline-list.spec.tsx index 29498ead7fd..61a86eb6f35 100644 --- a/web/app/components/datasets/create-from-pipeline/list/__tests__/built-in-pipeline-list.spec.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/__tests__/built-in-pipeline-list.spec.tsx @@ -1,11 +1,11 @@ import type { ReactElement } from 'react' import { screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import BuiltInPipelineList from '../built-in-pipeline-list' const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: true }, }) diff --git a/web/app/components/datasets/create/__tests__/index.spec.tsx b/web/app/components/datasets/create/__tests__/index.spec.tsx index 898a752b835..f8b9ebfec2b 100644 --- a/web/app/components/datasets/create/__tests__/index.spec.tsx +++ b/web/app/components/datasets/create/__tests__/index.spec.tsx @@ -6,10 +6,11 @@ import type { TopBarProps } from '../top-bar' import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types' import type { NotionPage } from '@/models/common' import type { DataSet, FileItem } from '@/models/datasets' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { DataSourceProvider } from '@/models/common' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' +import { render } from '@/test/console/render' import { RETRIEVE_METHOD } from '@/types/app' import DatasetUpdateForm from '../index' @@ -41,51 +42,37 @@ let mockCurrentUserId = 'user-1' let mockWorkspacePermissionKeys = ['dataset.create_and_management'] let mockIsLoadingWorkspacePermissionKeys = false -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createAccountStateModuleMock(() => ({ userProfile: { id: mockCurrentUserId }, workspacePermissionKeys: mockWorkspacePermissionKeys, isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createWorkspaceStateModuleMock(() => ({ userProfile: { id: mockCurrentUserId }, workspacePermissionKeys: mockWorkspacePermissionKeys, isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createPermissionStateModuleMock(() => ({ userProfile: { id: mockCurrentUserId }, workspacePermissionKeys: mockWorkspacePermissionKeys, isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ - userProfile: { id: mockCurrentUserId }, - workspacePermissionKeys: mockWorkspacePermissionKeys, - isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createSystemFeaturesStateModuleMock(() => ({ userProfile: { id: mockCurrentUserId }, workspacePermissionKeys: mockWorkspacePermissionKeys, isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, @@ -110,13 +97,6 @@ vi.mock('@/context/modal-context', () => ({ }, })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - // Mock dataset detail context let mockDatasetDetail: DataSet | undefined vi.mock('@/context/dataset-detail', () => ({ diff --git a/web/app/components/datasets/documents/__tests__/index.spec.tsx b/web/app/components/datasets/documents/__tests__/index.spec.tsx index 293ddeb46f8..928e5e9f34b 100644 --- a/web/app/components/datasets/documents/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/__tests__/index.spec.tsx @@ -1,9 +1,10 @@ import type { DocumentListResponse } from '@/models/datasets' -import { act, fireEvent, render, screen } from '@testing-library/react' +import { act, fireEvent, screen } from '@testing-library/react' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import { useProviderContext } from '@/context/provider-context' import { DataSourceType } from '@/models/datasets' import { useDocumentList } from '@/service/knowledge/use-document' +import { render } from '@/test/console/render' import { useDocumentsPageState } from '../hooks/use-documents-page-state' import Documents from '../index' @@ -47,47 +48,34 @@ vi.mock('@/context/provider-context', () => ({ })), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createAccountStateModuleMock(() => ({ userProfile: { id: 'test-user' }, workspacePermissionKeys: ['dataset.create_and_management'], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createWorkspaceStateModuleMock(() => ({ userProfile: { id: 'test-user' }, workspacePermissionKeys: ['dataset.create_and_management'], })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'test-user' }, workspacePermissionKeys: ['dataset.create_and_management'], })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ - userProfile: { id: 'test-user' }, - workspacePermissionKeys: ['dataset.create_and_management'], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createSystemFeaturesStateModuleMock(() => ({ userProfile: { id: 'test-user' }, workspacePermissionKeys: ['dataset.create_and_management'], })) @@ -130,13 +118,6 @@ vi.mock('@/service/knowledge/use-document', () => ({ useInvalidDocumentDetail: vi.fn(() => mockInvalidDocumentDetail), })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - // Mock segment service hooks vi.mock('@/service/knowledge/use-segment', () => ({ useSegmentListKey: 'segment-list-key', @@ -629,7 +610,9 @@ describe('Documents', () => { render() - const payload = vi.mocked(useDocumentList).mock.calls.at(-1)?.[0] + const payload = vi + .mocked(useDocumentList) + .mock.calls.find(([options]) => options.query.status === 'indexing')?.[0] const refetchInterval = payload?.refetchInterval expect(typeof refetchInterval).toBe('function') if (typeof refetchInterval !== 'function') diff --git a/web/app/components/datasets/documents/components/__tests__/list.spec.tsx b/web/app/components/datasets/documents/components/__tests__/list.spec.tsx index 0ce6276ea96..f9f23767864 100644 --- a/web/app/components/datasets/documents/components/__tests__/list.spec.tsx +++ b/web/app/components/datasets/documents/components/__tests__/list.spec.tsx @@ -1,6 +1,7 @@ import type { SimpleDocumentDetail } from '@/models/datasets' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { useDocumentSort } from '../document-list/hooks' import DocumentList from '../list' @@ -14,6 +15,13 @@ const mockShowEditModal = vi.fn() const mockHideEditModal = vi.fn() const mockHandleSave = vi.fn() +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('../document-list/hooks', () => ({ useDocumentSort: vi.fn(() => ({ sortField: null, @@ -133,6 +141,14 @@ const defaultProps = { onSortChange: vi.fn(), } +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('DocumentList', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/datasets/documents/components/document-list/__tests__/index.spec.tsx b/web/app/components/datasets/documents/components/document-list/__tests__/index.spec.tsx index 5803dde6c21..9b55bb8099f 100644 --- a/web/app/components/datasets/documents/components/document-list/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/components/document-list/__tests__/index.spec.tsx @@ -1,9 +1,11 @@ import type { ReactNode } from 'react' import type { SimpleDocumentDetail } from '@/models/datasets' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { act, fireEvent, render, screen } from '@testing-library/react' +import { act, fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ChunkingMode, DataSourceType } from '@/models/datasets' +import { seedAccountProfileQuery } from '@/test/console/account-profile' +import { render } from '@/test/console/render' import DocumentList from '../../list' const mockPush = vi.fn() @@ -38,47 +40,34 @@ vi.mock('@/context/dataset-detail', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createAccountStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['dataset.create_and_management'], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createWorkspaceStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['dataset.create_and_management'], })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['dataset.create_and_management'], })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: ['dataset.create_and_management'], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createSystemFeaturesStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['dataset.create_and_management'], })) @@ -94,23 +83,21 @@ vi.mock('@/app/components/datasets/metadata/hooks/use-batch-edit-document-metada }), })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { - queries: { retry: false, gcTime: 0 }, + queries: { + retry: false, + gcTime: Number.POSITIVE_INFINITY, + staleTime: Number.POSITIVE_INFINITY, + }, mutations: { retry: false }, }, }) const createWrapper = () => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() + seedAccountProfileQuery(queryClient) return ({ children }: { children: ReactNode }) => ( {children} ) diff --git a/web/app/components/datasets/documents/components/document-list/components/__tests__/document-table-row.spec.tsx b/web/app/components/datasets/documents/components/document-list/components/__tests__/document-table-row.spec.tsx index 192f59e508f..25fb82479db 100644 --- a/web/app/components/datasets/documents/components/document-list/components/__tests__/document-table-row.spec.tsx +++ b/web/app/components/datasets/documents/components/document-list/components/__tests__/document-table-row.spec.tsx @@ -1,15 +1,24 @@ import type { ReactNode } from 'react' import type { SimpleDocumentDetail } from '@/models/datasets' import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { DataSourceType } from '@/models/datasets' +import { createAccountProfileQueryClient } from '@/test/console/account-profile' +import { createQueryClientWrapper } from '@/test/console/query-client' +import { render } from '@/test/console/render' import DocumentTableRow from '../document-table-row' const mockPush = vi.fn() let mockSearchParams = '' +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/next/navigation', () => ({ useRouter: () => ({ push: mockPush, @@ -17,18 +26,10 @@ vi.mock('@/next/navigation', () => ({ useSearchParams: () => new URLSearchParams(mockSearchParams), })) -const createTestQueryClient = () => - new QueryClient({ - defaultOptions: { - queries: { retry: false, gcTime: 0 }, - mutations: { retry: false }, - }, - }) - const createWrapper = (value: string[] = [], onValueChange = vi.fn()) => { - const queryClient = createTestQueryClient() + const QueryClientWrapper = createQueryClientWrapper(createAccountProfileQueryClient()) return ({ children }: { children: ReactNode }) => ( - + onValueChange(nextValue)} @@ -38,7 +39,7 @@ const createWrapper = (value: string[] = [], onValueChange = vi.fn()) => { {children} - + ) } @@ -83,6 +84,14 @@ const createMockDoc = (overrides: Record = {}): LocalDoc => const getRowCheckbox = () => screen.getByRole('checkbox', { name: 'test-document.txt' }) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('DocumentTableRow', () => { const defaultProps = { doc: createMockDoc(), diff --git a/web/app/components/datasets/documents/components/document-list/hooks/__tests__/use-document-actions-with-query-client.spec.tsx b/web/app/components/datasets/documents/components/document-list/hooks/__tests__/use-document-actions-with-query-client.spec.tsx index 9ae1115ad74..580c3b5d5d6 100644 --- a/web/app/components/datasets/documents/components/document-list/hooks/__tests__/use-document-actions-with-query-client.spec.tsx +++ b/web/app/components/datasets/documents/components/document-list/hooks/__tests__/use-document-actions-with-query-client.spec.tsx @@ -16,7 +16,7 @@ const mockUseDocumentDelete = vi.mocked(useDocument.useDocumentDelete) const mockUseDocumentBatchRetryIndex = vi.mocked(useDocument.useDocumentBatchRetryIndex) const mockUseDocumentDownloadZip = vi.mocked(useDocument.useDocumentDownloadZip) -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false }, @@ -25,7 +25,7 @@ const createTestQueryClient = () => }) const createWrapper = () => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() return ({ children }: { children: ReactNode }) => ( {children} ) diff --git a/web/app/components/datasets/documents/create-from-pipeline/__tests__/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/__tests__/index.spec.tsx index 9ec8949b1ec..9f1c478f768 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/__tests__/index.spec.tsx @@ -1,4 +1,5 @@ -import { render, waitFor } from '@testing-library/react' +import { waitFor } from '@testing-library/react' +import { render } from '@/test/console/render' import CreateFromPipeline from '../index' let mockDatasetPermissionKeys = ['dataset.acl.use'] @@ -15,63 +16,46 @@ vi.mock('@/context/provider-context', () => ({ ) => selector({ plan: mockPlan, enableBilling: true }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - return createDatasetAccessAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + + return createAccountStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['dataset.create_and_management'], isLoadingWorkspacePermissionKeys: false, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - return createDatasetAccessAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + + return createWorkspaceStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['dataset.create_and_management'], isLoadingWorkspacePermissionKeys: false, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - return createDatasetAccessAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['dataset.create_and_management'], isLoadingWorkspacePermissionKeys: false, })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - return createDatasetAccessAtomMock(importOriginal, () => ({ +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') + + return createSystemFeaturesStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['dataset.create_and_management'], isLoadingWorkspacePermissionKeys: false, })) }) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - return createDatasetAccessAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: ['dataset.create_and_management'], - isLoadingWorkspacePermissionKeys: false, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - vi.mock('@/context/dataset-detail', () => ({ useDatasetDetailContextWithSelector: ( selector: (state: { diff --git a/web/app/components/datasets/documents/detail/__tests__/index.spec.tsx b/web/app/components/datasets/documents/detail/__tests__/index.spec.tsx index d6541240a2b..390dc673bb4 100644 --- a/web/app/components/datasets/documents/detail/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/detail/__tests__/index.spec.tsx @@ -1,5 +1,6 @@ -import { act, fireEvent, render, screen } from '@testing-library/react' +import { act, fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { DatasetACLPermission } from '@/utils/permission' // --- All hoisted mock fns and state (accessible inside vi.mock factories) --- @@ -29,6 +30,13 @@ const mocks = vi.hoisted(() => { }) // --- External mocks --- +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/next/navigation', () => ({ useRouter: () => ({ push: mocks.push }), useSearchParams: () => new URLSearchParams(mocks.state.searchParams), @@ -260,6 +268,14 @@ const createDocumentDetail = (overrides?: Record) => ({ ...overrides, }) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('DocumentDetail', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/datasets/documents/detail/embedding/__tests__/index.spec.tsx b/web/app/components/datasets/documents/detail/embedding/__tests__/index.spec.tsx index 8c68a6ba946..1f3cd6a84ca 100644 --- a/web/app/components/datasets/documents/detail/embedding/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/detail/embedding/__tests__/index.spec.tsx @@ -37,7 +37,7 @@ const mockPauseDocIndexing = vi.mocked(datasetsService.pauseDocIndexing) const mockResumeDocIndexing = vi.mocked(datasetsService.resumeDocIndexing) const mockUseProcessRule = vi.mocked(useDataset.useProcessRule) -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 }, @@ -48,7 +48,7 @@ const createTestQueryClient = () => const createWrapper = ( contextValue: DocumentContextValue = { datasetId: 'ds1', documentId: 'doc1' }, ) => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() return ({ children }: { children: ReactNode }) => ( <> diff --git a/web/app/components/datasets/documents/detail/embedding/hooks/__tests__/use-embedding-status.spec.tsx b/web/app/components/datasets/documents/detail/embedding/hooks/__tests__/use-embedding-status.spec.tsx index 19ea6c68c1c..737fc400e2c 100644 --- a/web/app/components/datasets/documents/detail/embedding/hooks/__tests__/use-embedding-status.spec.tsx +++ b/web/app/components/datasets/documents/detail/embedding/hooks/__tests__/use-embedding-status.spec.tsx @@ -19,7 +19,7 @@ const mockFetchIndexingStatus = vi.mocked(datasetsService.fetchIndexingStatus) const mockPauseDocIndexing = vi.mocked(datasetsService.pauseDocIndexing) const mockResumeDocIndexing = vi.mocked(datasetsService.resumeDocIndexing) -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false }, @@ -28,7 +28,7 @@ const createTestQueryClient = () => }) const createWrapper = () => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() return ({ children }: { children: ReactNode }) => ( {children} ) diff --git a/web/app/components/datasets/extra-info/api-access/__tests__/card.spec.tsx b/web/app/components/datasets/extra-info/api-access/__tests__/card.spec.tsx index 9c0a5448e36..de3ec933c22 100644 --- a/web/app/components/datasets/extra-info/api-access/__tests__/card.spec.tsx +++ b/web/app/components/datasets/extra-info/api-access/__tests__/card.spec.tsx @@ -1,5 +1,6 @@ -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { DatasetACLPermission } from '@/utils/permission' import Card from '../card' @@ -9,6 +10,13 @@ let mockDatasetPermissionKeys: string[] = [DatasetACLPermission.Edit] const mockEnableApi = vi.fn() const mockDisableApi = vi.fn() +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/context/dataset-detail', () => ({ useDatasetDetailContextWithSelector: (selector: (state: Record) => unknown) => selector({ @@ -29,6 +37,14 @@ vi.mock('@/hooks/use-api-access-url', () => ({ useDatasetApiAccessUrl: () => 'https://docs.dify.ai/api-reference/datasets', })) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('API access card', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/datasets/extra-info/service-api/__tests__/index.spec.tsx b/web/app/components/datasets/extra-info/service-api/__tests__/index.spec.tsx index 6027105c7cd..4985a8dc546 100644 --- a/web/app/components/datasets/extra-info/service-api/__tests__/index.spec.tsx +++ b/web/app/components/datasets/extra-info/service-api/__tests__/index.spec.tsx @@ -1,15 +1,16 @@ -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import ServiceApi from '../index' let mockPermissionKeys = ['dataset.api_key.manage'] -vi.mock('jotai', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - useAtomValue: () => mockPermissionKeys, - } +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockPermissionKeys, + })) }) vi.mock('@/app/components/develop/secret-key/secret-key-modal', () => ({ diff --git a/web/app/components/datasets/hit-testing/__tests__/index.spec.tsx b/web/app/components/datasets/hit-testing/__tests__/index.spec.tsx index ca4bffe8323..b3ea9e4e3df 100644 --- a/web/app/components/datasets/hit-testing/__tests__/index.spec.tsx +++ b/web/app/components/datasets/hit-testing/__tests__/index.spec.tsx @@ -2,9 +2,10 @@ import type { ReactNode } from 'react' import type { DataSet, HitTesting, HitTestingRecord, HitTestingResponse } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { RETRIEVE_METHOD } from '@/types/app' import HitTestingPage from '../index' @@ -117,7 +118,7 @@ const mockDataset = { permission_keys: ['dataset.acl.retrieval_recall'], } as Partial -let mockAppContextState = { +let mockConsoleState = { userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], } @@ -138,35 +139,25 @@ vi.mock('@/context/dataset-detail', () => ({ ), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createSystemFeaturesStateModuleMock(() => mockConsoleState) }) const mockRecordsRefetch = vi.fn() @@ -187,13 +178,6 @@ vi.mock('@/service/knowledge/use-dataset', () => ({ })), })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - vi.mock('@/service/knowledge/use-hit-testing', () => ({ useHitTesting: vi.fn(() => ({ mutateAsync: mockHitTestingMutateAsync, @@ -338,7 +322,7 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () // Test Wrapper with QueryClientProvider -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { @@ -352,7 +336,7 @@ const createTestQueryClient = () => }) const TestWrapper = ({ children }: { children: ReactNode }) => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() return {children} } @@ -433,7 +417,7 @@ describe('HitTestingPage', () => { is_multimodal: false, permission_keys: ['dataset.acl.retrieval_recall'], }) - mockAppContextState = { + mockConsoleState = { userProfile: { id: 'user-1' }, workspacePermissionKeys: [], } diff --git a/web/app/components/datasets/list/__tests__/index.spec.tsx b/web/app/components/datasets/list/__tests__/index.spec.tsx index b7b756ddbc5..a1d6ba7fbf9 100644 --- a/web/app/components/datasets/list/__tests__/index.spec.tsx +++ b/web/app/components/datasets/list/__tests__/index.spec.tsx @@ -1,11 +1,12 @@ import type { ReactNode } from 'react' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import List from '../index' const mockPush = vi.fn() const mockReplace = vi.fn() -let mockAppContextState = { +let mockConsoleState = { isCurrentWorkspaceEditor: true, isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: true, @@ -20,35 +21,25 @@ vi.mock('@/next/navigation', () => ({ // Mock app context -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createSystemFeaturesStateModuleMock(() => mockConsoleState) }) // Mock external api panel context @@ -60,13 +51,6 @@ vi.mock('@/context/external-api-panel-context', () => ({ }), })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - // Mock useDocumentTitle hook vi.mock('@/hooks/use-document-title', () => ({ default: vi.fn(), @@ -191,7 +175,7 @@ vi.mock('@/app/components/datasets/create/website/base/checkbox-with-label', () describe('List', () => { beforeEach(async () => { vi.clearAllMocks() - mockAppContextState = { + mockConsoleState = { isCurrentWorkspaceEditor: true, isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: true, @@ -224,7 +208,7 @@ describe('List', () => { }) it('should hide external API panel button without dataset.external.connect', () => { - mockAppContextState = { + mockConsoleState = { isCurrentWorkspaceEditor: true, isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: true, @@ -335,7 +319,7 @@ describe('List', () => { }) it('should render first empty state when dataset.create_and_management is available without the legacy editor role', async () => { - mockAppContextState = { + mockConsoleState = { isCurrentWorkspaceEditor: false, isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: true, @@ -359,7 +343,7 @@ describe('List', () => { }) it('should not render first empty state for legacy editors without dataset creation permissions', async () => { - mockAppContextState = { + mockConsoleState = { isCurrentWorkspaceEditor: true, isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: true, @@ -472,7 +456,7 @@ describe('List', () => { }) it('should not show ExternalAPIPanel without dataset.external.connect even when panel state is open', async () => { - mockAppContextState = { + mockConsoleState = { isCurrentWorkspaceEditor: true, isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: true, @@ -520,7 +504,7 @@ describe('List', () => { }) it('should not show include all checkbox when not workspace owner', async () => { - mockAppContextState = { + mockConsoleState = { isCurrentWorkspaceEditor: true, isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, diff --git a/web/app/components/datasets/list/dataset-card/__tests__/index.spec.tsx b/web/app/components/datasets/list/dataset-card/__tests__/index.spec.tsx index b8e78e15451..f82619adb3e 100644 --- a/web/app/components/datasets/list/dataset-card/__tests__/index.spec.tsx +++ b/web/app/components/datasets/list/dataset-card/__tests__/index.spec.tsx @@ -1,9 +1,10 @@ import type { DataSet } from '@/models/datasets' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { IndexingType } from '@/app/components/datasets/create/step-two' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' +import { render } from '@/test/console/render' import { DatasetACLPermission } from '@/utils/permission' import DatasetCardFooter from '../components/dataset-card-footer' import Description from '../components/description' @@ -55,41 +56,31 @@ vi.mock('@/next/navigation', () => ({ useRouter: () => ({ push: mockPush }), })) -let mockAppContextState = { +let mockConsoleState = { isCurrentWorkspaceDatasetOperator: false, userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], } -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState) + return createSystemFeaturesStateModuleMock(() => mockConsoleState) }) vi.mock('../hooks/use-dataset-card-state', () => ({ @@ -111,13 +102,6 @@ vi.mock('../hooks/use-dataset-card-state', () => ({ }), })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - vi.mock('../components/corner-labels', () => ({ default: () =>
, })) @@ -187,7 +171,7 @@ const createMockDataset = (overrides: Partial = {}): DataSet => describe('DatasetCard Integration', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContextState = { + mockConsoleState = { isCurrentWorkspaceDatasetOperator: false, userProfile: { id: 'user-1' }, workspacePermissionKeys: [], @@ -336,7 +320,7 @@ describe('DatasetCard Integration', () => { describe('DatasetCard Component', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContextState = { + mockConsoleState = { isCurrentWorkspaceDatasetOperator: false, userProfile: { id: 'user-1' }, workspacePermissionKeys: [], @@ -471,7 +455,7 @@ describe('DatasetCard Component', () => { }) it('should allow tag binding with workspace dataset tag management permission', () => { - mockAppContextState = { + mockConsoleState = { isCurrentWorkspaceDatasetOperator: false, userProfile: { id: 'user-1' }, workspacePermissionKeys: ['dataset.tag.manage'], diff --git a/web/app/components/datasets/list/dataset-card/components/__tests__/operations-dropdown.spec.tsx b/web/app/components/datasets/list/dataset-card/components/__tests__/operations-dropdown.spec.tsx index b7da2bc0dcd..17a919610be 100644 --- a/web/app/components/datasets/list/dataset-card/components/__tests__/operations-dropdown.spec.tsx +++ b/web/app/components/datasets/list/dataset-card/components/__tests__/operations-dropdown.spec.tsx @@ -1,92 +1,50 @@ import type { DataSet } from '@/models/datasets' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { IndexingType } from '@/app/components/datasets/create/step-two' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { DatasetACLPermission } from '@/utils/permission' import OperationsDropdown from '../operations-dropdown' -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], })) let mockIsRbacEnabled = true -const render = (ui: Parameters[0]) => - renderWithSystemFeatures(ui, { +const render = (ui: Parameters[0]) => + renderWithConsoleQuery(ui, { systemFeatures: { rbac_enabled: mockIsRbacEnabled, }, }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ + return createSystemFeaturesStateModuleMock(() => ({ + ...(() => mockConsoleState)(), + datasetRbacEnabled: (() => ({ isRbacEnabled: mockIsRbacEnabled, - }), - ) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) + }))().isRbacEnabled, + })) }) describe('OperationsDropdown', () => { @@ -129,8 +87,8 @@ describe('OperationsDropdown', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContextState.userProfile = { id: 'user-1' } - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.userProfile = { id: 'user-1' } + mockConsoleState.workspacePermissionKeys = [] mockIsRbacEnabled = true }) diff --git a/web/app/components/datasets/metadata/base/__tests__/date-picker.spec.tsx b/web/app/components/datasets/metadata/base/__tests__/date-picker.spec.tsx index 047713af3a3..4cebb3f43ee 100644 --- a/web/app/components/datasets/metadata/base/__tests__/date-picker.spec.tsx +++ b/web/app/components/datasets/metadata/base/__tests__/date-picker.spec.tsx @@ -1,7 +1,7 @@ import type { ReactElement } from 'react' import { fireEvent, render as rtlRender, screen } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' -import { createAccountProfileQueryWrapper } from '@/test/account-profile-query' +import { createAccountProfileQueryWrapper } from '@/test/console/account-profile' import WrappedDatePicker from '../date-picker' type TriggerArgs = { diff --git a/web/app/components/datasets/settings/form/__tests__/index.spec.tsx b/web/app/components/datasets/settings/form/__tests__/index.spec.tsx index 5f2d669804c..d6ae4d2e487 100644 --- a/web/app/components/datasets/settings/form/__tests__/index.spec.tsx +++ b/web/app/components/datasets/settings/form/__tests__/index.spec.tsx @@ -1,8 +1,9 @@ import type { DataSet } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' import { expectLoadingButton } from '@/test/button' +import { render } from '@/test/console/render' import { RETRIEVE_METHOD } from '@/types/app' import { DatasetACLPermission } from '@/utils/permission' import { IndexingType } from '../../../create/step-two' @@ -37,80 +38,42 @@ vi.mock('@tanstack/react-query', async (importOriginal) => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - }), - () => ({ - isRbacEnabled: false, - }), - ) + return createAccountStateModuleMock(() => ({ + userProfile: mockUserProfile, + workspacePermissionKeys: mockWorkspacePermissionKeys, + })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - }), - () => ({ - isRbacEnabled: false, - }), - ) + return createWorkspaceStateModuleMock(() => ({ + userProfile: mockUserProfile, + workspacePermissionKeys: mockWorkspacePermissionKeys, + })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - }), - () => ({ - isRbacEnabled: false, - }), - ) + return createPermissionStateModuleMock(() => ({ + userProfile: mockUserProfile, + workspacePermissionKeys: mockWorkspacePermissionKeys, + })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => ({ + return createSystemFeaturesStateModuleMock(() => ({ + ...(() => ({ userProfile: mockUserProfile, workspacePermissionKeys: mockWorkspacePermissionKeys, - }), - () => ({ + }))(), + datasetRbacEnabled: (() => ({ isRbacEnabled: false, - }), - ) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock( - importOriginal, - () => ({ - userProfile: mockUserProfile, - workspacePermissionKeys: mockWorkspacePermissionKeys, - }), - () => ({ - isRbacEnabled: false, - }), - ) + }))().isRbacEnabled, + })) }) const createMockDataset = (overrides: Partial = {}): DataSet => ({ @@ -197,13 +160,6 @@ vi.mock('@/context/dataset-detail', () => ({ }, })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - // Mock services vi.mock('@/service/datasets', () => ({ updateDatasetSetting: vi.fn().mockResolvedValue({}), diff --git a/web/app/components/datasets/settings/form/components/__tests__/basic-info-section.spec.tsx b/web/app/components/datasets/settings/form/components/__tests__/basic-info-section.spec.tsx index b73dcff4e97..e654d45fcfd 100644 --- a/web/app/components/datasets/settings/form/components/__tests__/basic-info-section.spec.tsx +++ b/web/app/components/datasets/settings/form/components/__tests__/basic-info-section.spec.tsx @@ -1,8 +1,9 @@ import type { Member } from '@/models/common' import type { DataSet, IconInfo } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' +import { render } from '@/test/console/render' import { RETRIEVE_METHOD } from '@/types/app' import { IndexingType } from '../../../../create/step-two' import BasicInfoSection from '../basic-info-section' @@ -19,7 +20,7 @@ vi.mock('@tanstack/react-query', async (importOriginal) => { } }) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1', name: 'Current User', @@ -31,65 +32,30 @@ const mockAppContextState = vi.hoisted(() => ({ // Mock app-context -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: false, - }), - ) + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: false, - }), - ) + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: false, - }), - ) + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ + return createSystemFeaturesStateModuleMock(() => ({ + ...(() => mockConsoleState)(), + datasetRbacEnabled: (() => ({ isRbacEnabled: false, - }), - ) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: false, - }), - ) + }))().isRbacEnabled, + })) }) // Mock image uploader hooks for AppIconPicker @@ -109,13 +75,6 @@ vi.mock('@/app/components/base/image-uploader/hooks', () => ({ }), })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - describe('BasicInfoSection', () => { const mockDataset: DataSet = { id: 'dataset-1', diff --git a/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts b/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts index 8c7276877b3..e25afd3c663 100644 --- a/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts +++ b/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts @@ -1,12 +1,13 @@ import type { DataSet } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' -import { act, renderHook, waitFor } from '@testing-library/react' +import { act, waitFor } from '@testing-library/react' import { ChunkingMode, DatasetPermission, DataSourceType, WeightedScoreEnum, } from '@/models/datasets' +import { renderHook } from '@/test/console/render' import { RETRIEVE_METHOD } from '@/types/app' import { DatasetACLPermission } from '@/utils/permission' import { IndexingType } from '../../../../create/step-two' @@ -21,47 +22,34 @@ const { mockToastSuccess, mockToastError } = vi.hoisted(() => ({ const mockMutateDatasets = vi.fn() const mockInvalidDatasetList = vi.fn() -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createAccountStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: [], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createWorkspaceStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: [], })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: [], })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: [], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock(importOriginal, () => ({ + return createSystemFeaturesStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: [], })) @@ -150,13 +138,6 @@ vi.mock('@/context/dataset-detail', () => ({ }, })) -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) -}) - // Mock services vi.mock('@/service/datasets', () => ({ updateDatasetSetting: vi.fn().mockResolvedValue({}), diff --git a/web/app/components/datasets/settings/permission-selector/__tests__/index.spec.tsx b/web/app/components/datasets/settings/permission-selector/__tests__/index.spec.tsx index fe1930e050d..cca0fe8f85d 100644 --- a/web/app/components/datasets/settings/permission-selector/__tests__/index.spec.tsx +++ b/web/app/components/datasets/settings/permission-selector/__tests__/index.spec.tsx @@ -1,10 +1,10 @@ import type { Member } from '@/models/common' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { DatasetPermission } from '@/models/datasets' +import { renderWithConsoleQuery } from '@/test/console/query-data' import PermissionSelector from '../index' -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1', name: 'Current User', @@ -16,72 +16,30 @@ const mockAppContextState = vi.hoisted(() => ({ let mockIsRbacEnabled = false -vi.mock('@/context/account-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') +vi.mock('@/context/system-features-state', async () => { + const { createSystemFeaturesStateModuleMock } = await import('@/test/console/state-fixture') - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ + return createSystemFeaturesStateModuleMock(() => ({ + ...(() => mockConsoleState)(), + datasetRbacEnabled: (() => ({ isRbacEnabled: mockIsRbacEnabled, - }), - ) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createDatasetAccessAtomMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessAtomMock( - importOriginal, - () => mockAppContextState, - () => ({ - isRbacEnabled: mockIsRbacEnabled, - }), - ) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createDatasetAccessJotaiMock } = - await import('@/app/components/datasets/__tests__/mock-dataset-access') - - return createDatasetAccessJotaiMock(importOriginal) + }))().isRbacEnabled, + })) }) describe('PermissionSelector', () => { @@ -151,21 +109,21 @@ describe('PermissionSelector', () => { describe('Rendering', () => { it('should render Only Me option when permission is onlyMe', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) expect(screen.getByText(/form\.permissionsOnlyMe/))!.toBeInTheDocument() }) it('should render All Team Members option when permission is allTeamMembers', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) expect(screen.getByText(/form\.permissionsAllMember/))!.toBeInTheDocument() }) it('should render selected member names when permission is partialMembers', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( { describe('Dropdown Toggle', () => { it('should open dropdown when clicked', async () => { - renderWithSystemFeatures() + renderWithConsoleQuery() const trigger = screen.getByText(/form\.permissionsOnlyMe/) fireEvent.click(trigger) @@ -192,7 +150,7 @@ describe('PermissionSelector', () => { }) it('should not open dropdown when disabled', () => { - renderWithSystemFeatures() + renderWithConsoleQuery() const trigger = screen.getByText(/form\.permissionsOnlyMe/) fireEvent.click(trigger) @@ -205,7 +163,7 @@ describe('PermissionSelector', () => { describe('Permission Selection', () => { it('should call onChange with onlyMe when Only Me is selected', async () => { const handleChange = vi.fn() - renderWithSystemFeatures( + renderWithConsoleQuery( { it('should call onChange with allTeamMembers when All Team Members is selected', async () => { const handleChange = vi.fn() - renderWithSystemFeatures() + renderWithConsoleQuery() const trigger = screen.getByText(/form\.permissionsOnlyMe/) fireEvent.click(trigger) @@ -242,7 +200,7 @@ describe('PermissionSelector', () => { it('should call onChange with partialMembers when Invited Members is selected', async () => { const handleChange = vi.fn() const handleMemberSelect = vi.fn() - renderWithSystemFeatures( + renderWithConsoleQuery( { describe('Member Selection', () => { it('should show member list when partialMembers is selected', async () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -282,7 +240,7 @@ describe('PermissionSelector', () => { it('should call onMemberSelect when a member is clicked', async () => { const handleMemberSelect = vi.fn() - renderWithSystemFeatures( + renderWithConsoleQuery( { it('should deselect member when clicked again', async () => { const handleMemberSelect = vi.fn() - renderWithSystemFeatures( + renderWithConsoleQuery( { describe('Search Functionality', () => { it('should allow typing in search input', async () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -342,7 +300,7 @@ describe('PermissionSelector', () => { }) it('should render search input in partial members mode', async () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -355,7 +313,7 @@ describe('PermissionSelector', () => { }) it('should filter members after debounce completes', async () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -378,7 +336,7 @@ describe('PermissionSelector', () => { }) it('should handle clear search functionality', async () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -402,7 +360,7 @@ describe('PermissionSelector', () => { }) it('should filter members by email', async () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -425,7 +383,7 @@ describe('PermissionSelector', () => { }) it('should show no results message when search matches nothing', async () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -448,7 +406,7 @@ describe('PermissionSelector', () => { }) it('should show current user when search matches user name', async () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -471,7 +429,7 @@ describe('PermissionSelector', () => { }) it('should show current user when search matches user email', async () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -494,7 +452,7 @@ describe('PermissionSelector', () => { describe('Disabled State', () => { it('should apply disabled styles when disabled', () => { - const { container } = renderWithSystemFeatures( + const { container } = renderWithConsoleQuery( , ) // When disabled, the component has cursor-not-allowed! class (escaped in Tailwind) @@ -505,7 +463,7 @@ describe('PermissionSelector', () => { it('should show access config hint and remain closed when RBAC is enabled', () => { mockIsRbacEnabled = true - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { rbac_enabled: true, }, @@ -521,7 +479,7 @@ describe('PermissionSelector', () => { describe('Display Variations', () => { it('should display single avatar when only one member selected', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( { }) it('should display two avatars when two or more members selected', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( { describe('Edge Cases', () => { it('should handle empty member list', () => { - renderWithSystemFeatures() + renderWithConsoleQuery() expect(screen.getByText(/form\.permissionsOnlyMe/))!.toBeInTheDocument() }) it('should handle member list with only current user', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -584,7 +542,7 @@ describe('PermissionSelector', () => { }, ] - renderWithSystemFeatures( + renderWithConsoleQuery( { describe('Props', () => { it('should update when permission prop changes', () => { - const { rerender } = renderWithSystemFeatures( + const { rerender } = renderWithConsoleQuery( , ) diff --git a/web/app/components/detail-sidebar/__tests__/index.spec.tsx b/web/app/components/detail-sidebar/__tests__/index.spec.tsx index 90ea5423927..cbbb85ddfdd 100644 --- a/web/app/components/detail-sidebar/__tests__/index.spec.tsx +++ b/web/app/components/detail-sidebar/__tests__/index.spec.tsx @@ -1,4 +1,5 @@ -import { act, fireEvent, render, screen } from '@testing-library/react' +import { act, fireEvent, screen } from '@testing-library/react' +import { render } from '@/test/console/render' import { DetailSidebarFrame } from '..' import { DETAIL_SIDEBAR_STORAGE_KEY } from '../storage' @@ -11,7 +12,7 @@ const { hotkeyRegistrations } = vi.hoisted(() => ({ } >(), })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { langGeniusVersionInfo: { current_env: '', @@ -33,31 +34,9 @@ vi.mock('@tanstack/react-hotkeys', async (importOriginal) => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState.current) }) vi.mock('@/app/components/main-nav/components/account-section', () => ({ @@ -103,7 +82,7 @@ describe('DetailSidebarFrame', () => { beforeEach(() => { localStorage.clear() hotkeyRegistrations.clear() - mockAppContextState.current = { + mockConsoleState.current = { langGeniusVersionInfo: { current_env: '', }, @@ -131,7 +110,7 @@ describe('DetailSidebarFrame', () => { }) it('collapses detail content from the top toggle and hides environment metadata', () => { - mockAppContextState.current = { + mockConsoleState.current = { langGeniusVersionInfo: { current_env: 'TESTING', }, diff --git a/web/app/components/develop/__tests__/index.spec.tsx b/web/app/components/develop/__tests__/index.spec.tsx index 48f7b59423d..28df5e15f82 100644 --- a/web/app/components/develop/__tests__/index.spec.tsx +++ b/web/app/components/develop/__tests__/index.spec.tsx @@ -1,7 +1,15 @@ -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import DevelopMain from '../index' const mockAppDetailValue: { current: unknown } = { current: undefined } +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/app/components/app/store', () => ({ useStore: (selector: (state: unknown) => unknown) => { const state = { appDetail: mockAppDetailValue.current } @@ -23,6 +31,14 @@ vi.mock('@/app/components/develop/ApiServer', () => ({ ), })) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('DevelopMain', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/develop/secret-key/__tests__/secret-key-modal.spec.tsx b/web/app/components/develop/secret-key/__tests__/secret-key-modal.spec.tsx index 9086381100d..121f1c7a19b 100644 --- a/web/app/components/develop/secret-key/__tests__/secret-key-modal.spec.tsx +++ b/web/app/components/develop/secret-key/__tests__/secret-key-modal.spec.tsx @@ -1,6 +1,7 @@ -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { afterEach } from 'vitest' +import { render } from '@/test/console/render' import SecretKeyModal from '../secret-key-modal' async function renderModal(ui: React.ReactElement) { @@ -27,58 +28,14 @@ const mockCurrentWorkspace = vi.fn().mockReturnValue({ const mockIsCurrentWorkspaceManager = vi.fn().mockReturnValue(true) const mockIsCurrentWorkspaceEditor = vi.fn().mockReturnValue(true) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ currentWorkspace: mockCurrentWorkspace(), isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: mockCurrentWorkspace(), - isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), - isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: mockCurrentWorkspace(), - isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), - isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: mockCurrentWorkspace(), - isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), - isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: mockCurrentWorkspace(), - isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), - isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/hooks/use-timestamp', () => ({ default: () => ({ diff --git a/web/app/components/explore/app-card/__tests__/index.spec.tsx b/web/app/components/explore/app-card/__tests__/index.spec.tsx index ec4f22288af..8abe4db3a5d 100644 --- a/web/app/components/explore/app-card/__tests__/index.spec.tsx +++ b/web/app/components/explore/app-card/__tests__/index.spec.tsx @@ -3,8 +3,8 @@ import type { App } from '@/models/explore' import { fireEvent, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' import { trackEvent } from '@/app/components/base/amplitude' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import AppCard from '../index' diff --git a/web/app/components/explore/app-list/__tests__/index.spec.tsx b/web/app/components/explore/app-list/__tests__/index.spec.tsx index fcad4c0ffff..03b3008ca03 100644 --- a/web/app/components/explore/app-list/__tests__/index.spec.tsx +++ b/web/app/components/explore/app-list/__tests__/index.spec.tsx @@ -6,15 +6,15 @@ import type { App } from '@/models/explore' import type { App as WorkspaceApp } from '@/types/app' import { act, fireEvent, screen, waitFor } from '@testing-library/react' import { createStore, Provider as JotaiProvider } from 'jotai' -import { createSystemFeaturesWrapper } from '@/__tests__/utils/mock-system-features' import { fetchAppDetail, fetchAppList, fetchBanners } from '@/service/explore' +import { createConsoleQueryWrapper } from '@/test/console/query-data' import { renderWithNuqs } from '@/test/nuqs-testing' import { AppModeEnum } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import { LEARN_DIFY_HIDDEN_STORAGE_KEY } from '../../learn-dify/storage' import AppList from '../index' -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], })) @@ -150,37 +150,13 @@ vi.mock('@/service/client', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) vi.mock('@/hooks/use-import-dsl', () => ({ @@ -347,9 +323,7 @@ const createBanner = (overrides: Partial = {}): BannerType => ({ }) const mockAppCreatePermission = (hasEditPermission: boolean) => { - mockAppContextState.workspacePermissionKeys = hasEditPermission - ? ['app.create_and_management'] - : [] + mockConsoleState.workspacePermissionKeys = hasEditPermission ? ['app.create_and_management'] : [] } type RenderOptions = { @@ -370,7 +344,7 @@ const renderAppList = ( ) => { mockConfig.isCloudEdition = options.isCloudEdition ?? false mockAppCreatePermission(hasEditPermission) - const { wrapper: SystemFeaturesWrapper, queryClient } = createSystemFeaturesWrapper({ + const { wrapper: ConsoleQueryWrapper, queryClient } = createConsoleQueryWrapper({ systemFeatures: { enable_explore_banner: options.enableExploreBanner ?? false, enable_learn_app: options.enableLearnApp ?? true, @@ -404,7 +378,7 @@ const renderAppList = ( const Wrapped = ({ children }: { children: ReactNode }) => ( - {children} + {children} ) const rendered = renderWithNuqs( diff --git a/web/app/components/explore/banner/__tests__/banner.spec.tsx b/web/app/components/explore/banner/__tests__/banner.spec.tsx index 7e31372339b..ff2fd5182ad 100644 --- a/web/app/components/explore/banner/__tests__/banner.spec.tsx +++ b/web/app/components/explore/banner/__tests__/banner.spec.tsx @@ -1,8 +1,9 @@ import type { Banner as BannerType } from '@/models/app' -import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { cleanup, fireEvent, screen } from '@testing-library/react' import * as React from 'react' import { act } from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { Banner } from '../banner' const mockTrackEvent = vi.fn() @@ -14,7 +15,7 @@ const mockAutoplayListeners = { play: new Set<() => void>(), stop: new Set<() => void>(), } -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'account-123', name: 'Evan', @@ -57,15 +58,9 @@ const setMockSelectedIndex = (index: number) => { mockCarouselListeners.forEach((listener) => listener()) } -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('@/app/components/base/amplitude', () => ({ @@ -178,7 +173,7 @@ describe('Banner', () => { mockCarouselListeners.clear() mockAutoplayListeners.play.clear() mockAutoplayListeners.stop.clear() - mockAppContextState.userProfile = { id: 'account-123', name: 'Evan' } + mockConsoleState.userProfile = { id: 'account-123', name: 'Evan' } }) afterEach(cleanup) @@ -391,7 +386,7 @@ describe('Banner', () => { }) it('does not track impressions without an account id', () => { - mockAppContextState.userProfile = { id: '', name: '' } + mockConsoleState.userProfile = { id: '', name: '' } render() expect(mockTrackEvent).not.toHaveBeenCalled() diff --git a/web/app/components/explore/continue-work/__tests__/item.spec.tsx b/web/app/components/explore/continue-work/__tests__/item.spec.tsx index 5f052e64990..be97f56a1f3 100644 --- a/web/app/components/explore/continue-work/__tests__/item.spec.tsx +++ b/web/app/components/explore/continue-work/__tests__/item.spec.tsx @@ -1,13 +1,13 @@ import type { AnchorHTMLAttributes, ReactNode } from 'react' import type { App } from '@/types/app' import { fireEvent, screen } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { AccessMode } from '@/models/access-control' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import ContinueWorkItem from '../item' -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['app.create_and_management'], })) @@ -18,37 +18,13 @@ const toastMocks = vi.hoisted(() => ({ warning: vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) vi.mock('@/hooks/use-format-time-from-now', () => ({ @@ -107,16 +83,16 @@ const createApp = (overrides: Partial = {}): App => ({ const renderItem = ( app: App, - systemFeatures: NonNullable[1]>['systemFeatures'] = { + systemFeatures: NonNullable[1]>['systemFeatures'] = { rbac_enabled: true, }, -) => renderWithSystemFeatures(, { systemFeatures }) +) => renderWithConsoleQuery(, { systemFeatures }) describe('ContinueWorkItem', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContextState.userProfile = { id: 'user-1' } - mockAppContextState.workspacePermissionKeys = ['app.create_and_management'] + mockConsoleState.userProfile = { id: 'user-1' } + mockConsoleState.workspacePermissionKeys = ['app.create_and_management'] mockFormatTimeFromNow.mockReturnValue('5 minutes ago') }) diff --git a/web/app/components/explore/create-app-modal/__tests__/index.spec.tsx b/web/app/components/explore/create-app-modal/__tests__/index.spec.tsx index 792157e7c53..666ebaf8cb8 100644 --- a/web/app/components/explore/create-app-modal/__tests__/index.spec.tsx +++ b/web/app/components/explore/create-app-modal/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ import type { CreateAppModalProps } from '../index' import type { UsagePlanInfo } from '@/app/components/billing/type' -import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import { act, fireEvent, screen, waitFor, within } from '@testing-library/react' import * as React from 'react' import { createMockPlan, @@ -8,6 +8,7 @@ import { createMockPlanUsage, } from '@/__mocks__/provider-context' import { Plan } from '@/app/components/billing/type' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import CreateAppModal from '../index' diff --git a/web/app/components/explore/try-app/__tests__/index.spec.tsx b/web/app/components/explore/try-app/__tests__/index.spec.tsx index 4ecd6def49c..f83cedc4d16 100644 --- a/web/app/components/explore/try-app/__tests__/index.spec.tsx +++ b/web/app/components/explore/try-app/__tests__/index.spec.tsx @@ -1,7 +1,7 @@ import type { TryAppInfo } from '@/service/try-app' import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import TryApp from '../index' import { TypeEnum } from '../types' diff --git a/web/app/components/header/account-about/__tests__/index.spec.tsx b/web/app/components/header/account-about/__tests__/index.spec.tsx index d9a71c73018..01545443f55 100644 --- a/web/app/components/header/account-about/__tests__/index.spec.tsx +++ b/web/app/components/header/account-about/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ import type { LangGeniusVersionInfo } from '@/context/app-context-types' import { fireEvent, screen } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import AccountAbout from '../index' let mockIsCEEdition = false @@ -38,7 +38,7 @@ describe('AccountAbout', () => { describe('Rendering', () => { it('should render correctly with version information', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , { systemFeatures: { branding: { enabled: false } }, @@ -50,7 +50,7 @@ describe('AccountAbout', () => { }) it('should render branding logo if enabled', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , { systemFeatures: { branding: { enabled: true, workspace_logo: 'custom-logo.png' } }, @@ -65,7 +65,7 @@ describe('AccountAbout', () => { describe('Version Logic', () => { it('should show "Latest Available" when current version equals latest', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -75,7 +75,7 @@ describe('AccountAbout', () => { it('should show "Now Available" when current version is behind', () => { const behindVersionInfo = { ...mockVersionInfo, latest_version: '0.7.0' } - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -88,7 +88,7 @@ describe('AccountAbout', () => { it('should render correctly in Community Edition', () => { mockIsCEEdition = true - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -99,7 +99,7 @@ describe('AccountAbout', () => { mockIsCEEdition = true const behindVersionInfo = { ...mockVersionInfo, latest_version: '0.7.0' } - renderWithSystemFeatures( + renderWithConsoleQuery( , ) @@ -109,7 +109,7 @@ describe('AccountAbout', () => { describe('User Interactions', () => { it('should call onCancel when close button is clicked', () => { - renderWithSystemFeatures( + renderWithConsoleQuery( , ) diff --git a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx index 30b6f457bd4..01952df1fc8 100644 --- a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx +++ b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx @@ -1,11 +1,10 @@ import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { ModalContextState } from '@/context/modal-context' import type { ProviderContextState } from '@/context/provider-context' +import type { ConsoleStateFixture } from '@/test/console/state-fixture' import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { renderToString } from 'react-dom/server' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { Plan } from '@/app/components/billing/type' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import AccountSection from '@/app/components/main-nav/components/account-section' @@ -13,7 +12,8 @@ import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' import { useRouter } from '@/next/navigation' import { useLogout } from '@/service/use-common' -import { createAccountProfileQueryClient } from '@/test/account-profile-query' +import { createAccountProfileQueryClient } from '@/test/console/account-profile' +import { renderWithConsoleQuery } from '@/test/console/query-data' import AppSelector from '../index' type DeepPartial = @@ -51,10 +51,10 @@ vi.mock('@/app/components/base/theme-switcher', () => ({ const { mockSetTheme } = vi.hoisted(() => ({ mockSetTheme: vi.fn(), })) -const mockAppContextState = vi.hoisted(() => ({ - current: undefined as AppContextStateMockState | undefined, +const mockConsoleState = vi.hoisted(() => ({ + current: undefined as ConsoleStateFixture | undefined, })) -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) vi.mock('next-themes', () => ({ useTheme: () => ({ @@ -63,31 +63,21 @@ vi.mock('next-themes', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current ?? {}) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState.current ?? {}) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current ?? {}) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState.current ?? {}) }) vi.mock('@/context/provider-context', () => ({ @@ -159,9 +149,9 @@ const baseUserProfile = { is_password_set: false, } -const baseAppContextValue: AppContextStateMockState = { +const baseConsoleState: ConsoleStateFixture = { userProfile: baseUserProfile, - mutateUserProfile: vi.fn(), + refreshUserProfile: vi.fn(), currentWorkspace: { id: '1', name: 'Workspace', @@ -178,7 +168,7 @@ const baseAppContextValue: AppContextStateMockState = { isCurrentWorkspaceOwner: true, isCurrentWorkspaceEditor: true, isCurrentWorkspaceDatasetOperator: false, - mutateCurrentWorkspace: vi.fn(), + refreshCurrentWorkspace: vi.fn(), langGeniusVersionInfo: { current_env: 'testing', current_version: '0.6.0', @@ -192,9 +182,9 @@ const baseAppContextValue: AppContextStateMockState = { workspacePermissionKeys: [], } -const setAppContextValue = (value: AppContextStateMockState) => { - mockAppContextState.current = value - mockUseAppContext.mockReturnValue(value) +const setConsoleState = (value: ConsoleStateFixture) => { + mockConsoleState.current = value + mockConsoleStateReader.mockReturnValue(value) } describe('AccountDropdown', () => { @@ -208,9 +198,9 @@ describe('AccountDropdown', () => { ) => { const queryClient = createAccountProfileQueryClient({ ...baseUserProfile, - ...(mockAppContextState.current?.userProfile ?? {}), + ...(mockConsoleState.current?.userProfile ?? {}), }) - return renderWithSystemFeatures(ui, { + return renderWithConsoleQuery(ui, { queryClient, systemFeatures: options.systemFeatures ?? { branding: { enabled: false } }, }) @@ -222,7 +212,7 @@ describe('AccountDropdown', () => { mockConfig.IS_CLOUD_EDITION = false mockEnv.env.NEXT_PUBLIC_SITE_ABOUT = 'show' - setAppContextValue(baseAppContextValue) + setConsoleState(baseConsoleState) vi.mocked(useProviderContext).mockReturnValue({ isEducationAccount: false, plan: { type: Plan.sandbox }, @@ -257,7 +247,7 @@ describe('AccountDropdown', () => { avatar_url: 'current-avatar.png', }) - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { queryClient, systemFeatures: { branding: { enabled: false } }, }) @@ -353,12 +343,12 @@ describe('AccountDropdown', () => { it('should show Compliance in Cloud Edition for workspace owner', () => { // Arrange mockConfig.IS_CLOUD_EDITION = true - setAppContextValue({ - ...baseAppContextValue, - userProfile: { ...baseAppContextValue.userProfile, name: 'User' }, + setConsoleState({ + ...baseConsoleState, + userProfile: { ...baseConsoleState.userProfile, name: 'User' }, isCurrentWorkspaceOwner: true, langGeniusVersionInfo: { - ...baseAppContextValue.langGeniusVersionInfo, + ...baseConsoleState.langGeniusVersionInfo, current_version: '0.6.0', latest_version: '0.6.0', }, @@ -376,8 +366,8 @@ describe('AccountDropdown', () => { it('should hide Compliance in Cloud Edition when user is not workspace owner', () => { // Arrange mockConfig.IS_CLOUD_EDITION = true - setAppContextValue({ - ...baseAppContextValue, + setConsoleState({ + ...baseConsoleState, isCurrentWorkspaceOwner: false, }) @@ -476,11 +466,11 @@ describe('AccountDropdown', () => { describe('Version Indicators', () => { it('should show orange indicator when version is not latest', () => { // Arrange - setAppContextValue({ - ...baseAppContextValue, - userProfile: { ...baseAppContextValue.userProfile, name: 'User' }, + setConsoleState({ + ...baseConsoleState, + userProfile: { ...baseConsoleState.userProfile, name: 'User' }, langGeniusVersionInfo: { - ...baseAppContextValue.langGeniusVersionInfo, + ...baseConsoleState.langGeniusVersionInfo, current_version: '0.6.0', latest_version: '0.7.0', }, @@ -498,11 +488,11 @@ describe('AccountDropdown', () => { it('should show green indicator when version is latest', () => { // Arrange - setAppContextValue({ - ...baseAppContextValue, - userProfile: { ...baseAppContextValue.userProfile, name: 'User' }, + setConsoleState({ + ...baseConsoleState, + userProfile: { ...baseConsoleState.userProfile, name: 'User' }, langGeniusVersionInfo: { - ...baseAppContextValue.langGeniusVersionInfo, + ...baseConsoleState.langGeniusVersionInfo, current_version: '0.7.0', latest_version: '0.7.0', }, diff --git a/web/app/components/header/account-setting/__tests__/index.spec.tsx b/web/app/components/header/account-setting/__tests__/index.spec.tsx index a970928d7ab..cf51b7298f3 100644 --- a/web/app/components/header/account-setting/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/__tests__/index.spec.tsx @@ -1,10 +1,11 @@ import type { AccountSettingTab } from '../constants' -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' +import type { ConsoleStateFixture } from '@/test/console/state-fixture' import { fireEvent, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { useState } from 'react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { baseProviderContextValue, useProviderContext } from '@/context/provider-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { ACCOUNT_SETTING_TAB } from '../constants' import AccountSetting from '../index' @@ -12,7 +13,7 @@ const mockResetModelProviderListExpanded = vi.fn() const mockConfig = vi.hoisted(() => ({ IS_CLOUD_EDITION: true, })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: null as unknown, })) @@ -34,31 +35,21 @@ vi.mock('@/context/provider-context', async (importOriginal) => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current ?? {}) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState.current ?? {}) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current ?? {}) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState.current ?? {}) }) vi.mock('@/next/navigation', () => ({ @@ -88,21 +79,27 @@ vi.mock('next-themes', () => ({ })), })) -vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ - useDefaultModel: vi.fn(() => ({ data: null, isLoading: false })), - useLanguage: vi.fn(() => 'en-US'), - useUpdateDefaultModel: vi.fn(() => ({ trigger: vi.fn() })), - useUpdateModelList: vi.fn(() => vi.fn()), - useInvalidateDefaultModel: vi.fn(() => vi.fn()), - useModelList: vi.fn(() => ({ data: [], isLoading: false })), - useSystemDefaultModelAndModelList: vi.fn(() => [null, vi.fn()]), - useMarketplaceAllPlugins: vi.fn(() => ({ plugins: [], isLoading: false })), -})) - vi.mock('@/app/components/header/account-setting/model-provider-page/atoms', () => ({ useResetModelProviderListExpanded: () => mockResetModelProviderListExpanded, })) +vi.mock('@/app/components/header/account-setting/model-provider-page', () => ({ + default: ({ + onSearchTextChange, + searchText, + }: { + onSearchTextChange?: (value: string) => void + searchText: string + }) => ( + onSearchTextChange?.(event.target.value)} + /> + ), +})) + vi.mock('@/service/use-datasource', () => ({ useGetDataSourceListAuth: vi.fn(() => ({ data: { result: [] } })), })) @@ -159,7 +156,7 @@ vi.mock('@/app/components/header/account-setting/access-rules-page', () => ({ default: () =>
, })) -const baseAppContextValue: AppContextStateMockState = { +const baseConsoleState: ConsoleStateFixture = { userProfile: { id: '1', name: 'Test User', @@ -168,7 +165,7 @@ const baseAppContextValue: AppContextStateMockState = { avatar_url: '', is_password_set: false, }, - mutateUserProfile: vi.fn(), + refreshUserProfile: vi.fn(), currentWorkspace: { id: '1', name: 'Workspace', @@ -185,7 +182,7 @@ const baseAppContextValue: AppContextStateMockState = { isCurrentWorkspaceOwner: true, isCurrentWorkspaceEditor: true, isCurrentWorkspaceDatasetOperator: false, - mutateCurrentWorkspace: vi.fn(), + refreshCurrentWorkspace: vi.fn(), langGeniusVersionInfo: { current_env: 'testing', current_version: '0.1.0', @@ -239,7 +236,7 @@ describe('AccountSetting', () => { ) } - return renderWithSystemFeatures(, { + return renderWithConsoleQuery(, { systemFeatures: { webapp_auth: { enabled: true }, branding: { enabled: false }, @@ -258,7 +255,7 @@ describe('AccountSetting', () => { enableBilling: true, enableReplaceWebAppLogo: true, }) - mockAppContextState.current = baseAppContextValue + mockConsoleState.current = baseConsoleState vi.mocked(useBreakpoints).mockReturnValue(MediaType.pc) }) @@ -387,10 +384,10 @@ describe('AccountSetting', () => { it('should not hide workspace menu items solely for dataset operators', () => { // Arrange const datasetOperatorContext = { - ...baseAppContextValue, + ...baseConsoleState, isCurrentWorkspaceDatasetOperator: true, } - mockAppContextState.current = datasetOperatorContext + mockConsoleState.current = datasetOperatorContext // Act renderAccountSetting() @@ -414,12 +411,12 @@ describe('AccountSetting', () => { it('should keep moved integrations hidden when api extension permission is missing', () => { // Arrange const contextWithoutApiExtensionPermission = { - ...baseAppContextValue, - workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys!.filter( + ...baseConsoleState, + workspacePermissionKeys: baseConsoleState.workspacePermissionKeys!.filter( (key) => key !== 'api_extension.manage', ), } - mockAppContextState.current = contextWithoutApiExtensionPermission + mockConsoleState.current = contextWithoutApiExtensionPermission // Act renderAccountSetting() @@ -433,12 +430,12 @@ describe('AccountSetting', () => { it('should show custom tab when customization permission is missing', () => { // Arrange const contextWithoutCustomizationPermission = { - ...baseAppContextValue, - workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys!.filter( + ...baseConsoleState, + workspacePermissionKeys: baseConsoleState.workspacePermissionKeys!.filter( (key) => key !== 'customization.manage', ), } - mockAppContextState.current = contextWithoutCustomizationPermission + mockConsoleState.current = contextWithoutCustomizationPermission // Act renderAccountSetting() @@ -454,12 +451,12 @@ describe('AccountSetting', () => { it('should hide role and permission set entries when role management permission is missing', () => { // Arrange const contextWithoutRoleManagePermission = { - ...baseAppContextValue, - workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys!.filter( + ...baseConsoleState, + workspacePermissionKeys: baseConsoleState.workspacePermissionKeys!.filter( (key) => key !== 'workspace.role.manage', ), } - mockAppContextState.current = contextWithoutRoleManagePermission + mockConsoleState.current = contextWithoutRoleManagePermission // Act renderAccountSetting() @@ -518,12 +515,12 @@ describe('AccountSetting', () => { it('should hide billing entry when billing view permission is missing', () => { // Arrange const contextWithoutBillingViewPermission = { - ...baseAppContextValue, - workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys!.filter( + ...baseConsoleState, + workspacePermissionKeys: baseConsoleState.workspacePermissionKeys!.filter( (key) => key !== 'billing.view', ), } - mockAppContextState.current = contextWithoutBillingViewPermission + mockConsoleState.current = contextWithoutBillingViewPermission // Act renderAccountSetting() @@ -550,19 +547,19 @@ describe('AccountSetting', () => { it('should hide workflow log archives from custom RBAC roles that are not owner or admin', () => { // Arrange const contextWithRoleManagePermissionButNotManager = { - ...baseAppContextValue, + ...baseConsoleState, currentWorkspace: { - ...baseAppContextValue.currentWorkspace, + ...baseConsoleState.currentWorkspace, role: 'normal' as const, }, isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, workspacePermissionKeys: [ - ...(baseAppContextValue.workspacePermissionKeys ?? []), + ...(baseConsoleState.workspacePermissionKeys ?? []), 'workspace.role.manage', ], } - mockAppContextState.current = contextWithRoleManagePermissionButNotManager + mockConsoleState.current = contextWithRoleManagePermissionButNotManager // Act renderAccountSetting() @@ -588,12 +585,12 @@ describe('AccountSetting', () => { it('should not render billing page when active billing tab lacks billing view permission', () => { // Arrange const contextWithoutBillingViewPermission = { - ...baseAppContextValue, - workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys!.filter( + ...baseConsoleState, + workspacePermissionKeys: baseConsoleState.workspacePermissionKeys!.filter( (key) => key !== 'billing.view', ), } - mockAppContextState.current = contextWithoutBillingViewPermission + mockConsoleState.current = contextWithoutBillingViewPermission // Act renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.BILLING }) @@ -688,18 +685,17 @@ describe('AccountSetting', () => { expect(mockOnCancel).toHaveBeenCalled() }) - it('should update search value in provider tab', () => { + it('should keep provider search controlled at the account setting boundary', async () => { // Arrange + const user = userEvent.setup() renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.PROVIDER }) // Act const input = screen.getByRole('searchbox', { name: 'common.operation.search' }) - fireEvent.change(input, { target: { value: 'test-search' } }) + await user.type(input, 'test-search') - // Assert // Assert expect(input)!.toHaveValue('test-search') - expect(screen.getByPlaceholderText('common.modelProvider.searchModels'))!.toBeInTheDocument() }) it('should handle scroll event in panel', () => { diff --git a/web/app/components/header/account-setting/access-rules-page/__tests__/access-rule-section.spec.tsx b/web/app/components/header/account-setting/access-rules-page/__tests__/access-rule-section.spec.tsx index 3151647033e..8fbd7054571 100644 --- a/web/app/components/header/account-setting/access-rules-page/__tests__/access-rule-section.spec.tsx +++ b/web/app/components/header/account-setting/access-rules-page/__tests__/access-rule-section.spec.tsx @@ -1,50 +1,21 @@ import type { ReactNode } from 'react' import type { AccessPolicyWithBindings } from '@/models/access-control' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import AccessRuleSection from '../access-rule-section' const mocks = vi.hoisted(() => ({ workspacePermissionKeys: [] as string[], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mocks.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mocks.workspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mocks.workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mocks.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mocks.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) const rule: AccessPolicyWithBindings = { policy: { diff --git a/web/app/components/header/account-setting/api-based-extension-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/api-based-extension-page/__tests__/index.spec.tsx index b490bf3407f..d9a1e892e32 100644 --- a/web/app/components/header/account-setting/api-based-extension-page/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/api-based-extension-page/__tests__/index.spec.tsx @@ -1,5 +1,6 @@ import type { ApiBasedExtensionResponse } from '@dify/contracts/api/console/api-based-extension/types.gen' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { render } from '@/test/console/render' import { ApiBasedExtensionPage } from '../index' const { @@ -18,42 +19,12 @@ const { }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.current, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.current, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.current, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.current, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.current, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/client', () => ({ consoleQuery: { diff --git a/web/app/components/header/account-setting/data-source-page-new/__tests__/card.spec.tsx b/web/app/components/header/account-setting/data-source-page-new/__tests__/card.spec.tsx index 91892af4c14..35f629db634 100644 --- a/web/app/components/header/account-setting/data-source-page-new/__tests__/card.spec.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/__tests__/card.spec.tsx @@ -1,5 +1,5 @@ import type { DataSourceAuth } from '../types' -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import { fireEvent, screen, waitFor, within } from '@testing-library/react' import { FormTypeEnum } from '@/app/components/base/form/types' import { usePluginAuthAction } from '@/app/components/plugins/plugin-auth' import { CredentialTypeEnum } from '@/app/components/plugins/plugin-auth/types' @@ -13,6 +13,7 @@ import { useInvalidDefaultDataSourceListAuth, } from '@/service/use-datasource' import { useInvalidDataSourceList } from '@/service/use-pipeline' +import { render } from '@/test/console/render' import Card from '../card' import { useDataSourceAuthUpdate } from '../hooks' @@ -22,42 +23,12 @@ let mockWorkspacePermissionKeys: string[] = [ 'credential.manage', ] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/app/components/plugins/plugin-auth', () => ({ ApiKeyModal: vi.fn( diff --git a/web/app/components/header/account-setting/data-source-page-new/__tests__/index.spec.tsx b/web/app/components/header/account-setting/data-source-page-new/__tests__/index.spec.tsx index 5db6c0ea91f..6638a0b7a93 100644 --- a/web/app/components/header/account-setting/data-source-page-new/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/__tests__/index.spec.tsx @@ -3,7 +3,6 @@ import type { DataSourceAuth } from '../types' import type { PluginDetail } from '@/app/components/plugins/types' import { fireEvent, screen } from '@testing-library/react' import { useTheme } from 'next-themes' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { usePluginsWithLatestVersion } from '@/app/components/plugins/hooks' import { usePluginAuthAction } from '@/app/components/plugins/plugin-auth' import { useRenderI18nObject } from '@/hooks/use-i18n' @@ -18,6 +17,7 @@ import { useInstalledPluginList, useInvalidateInstalledPluginList, } from '@/service/use-plugins' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { useDataSourceAuthUpdate, useMarketplaceAllPlugins } from '../hooks' import DataSourcePage from '../index' @@ -88,11 +88,9 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({ })) vi.mock('@/app/components/header/account-setting/update-setting-dialog', () => ({ - __esModule: true, default: () => ( - ), })) @@ -227,7 +225,7 @@ describe('DataSourcePage Component', () => { } as unknown as UseQueryResult<{ result: DataSourceAuth[] }, Error>) // Act - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: false }, }) @@ -241,8 +239,9 @@ describe('DataSourcePage Component', () => { 'px-6', 'pb-2', ) - expect(screen.getByText('plugin.autoUpdate.autoUpdate')).toBeInTheDocument() - expect(screen.getAllByText('plugin.autoUpdate.strategy.fixOnly.name')[0]).toBeInTheDocument() + expect( + screen.getByRole('button', { name: 'plugin.autoUpdate.autoUpdate' }), + ).toBeInTheDocument() expect(screen.queryByText('Dify Source')).not.toBeInTheDocument() expect(screen.getByText('common.dataSourcePage.notSetUpTitle')).toBeInTheDocument() expect(screen.getByText('common.dataSourcePage.installFirst')).toBeInTheDocument() @@ -257,7 +256,7 @@ describe('DataSourcePage Component', () => { } as unknown as UseQueryResult<{ result: DataSourceAuth[] }, Error>) // Act - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: true }, }) @@ -276,7 +275,7 @@ describe('DataSourcePage Component', () => { } as unknown as UseQueryResult<{ result: DataSourceAuth[] }, Error>) // Act - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: false }, }) @@ -296,7 +295,7 @@ describe('DataSourcePage Component', () => { } as unknown as ReturnType) // Act - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: false }, }) @@ -311,7 +310,7 @@ describe('DataSourcePage Component', () => { } as unknown as UseQueryResult<{ result: DataSourceAuth[] }, Error>) // Act - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: true }, }) fireEvent.change(screen.getByPlaceholderText('common.operation.search'), { @@ -333,7 +332,7 @@ describe('DataSourcePage Component', () => { } as unknown as UseQueryResult<{ result: DataSourceAuth[] }, Error>) // Act - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: true }, }) @@ -349,7 +348,7 @@ describe('DataSourcePage Component', () => { } as unknown as UseQueryResult<{ result: DataSourceAuth[] }, Error>) // Act - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: true }, }) @@ -364,7 +363,7 @@ describe('DataSourcePage Component', () => { } as unknown as UseQueryResult<{ result: DataSourceAuth[] }, Error>) // Act - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: true }, }) @@ -380,7 +379,7 @@ describe('DataSourcePage Component', () => { } as unknown as UseQueryResult<{ result: DataSourceAuth[] }, Error>) // Act - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: false }, }) diff --git a/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx b/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx index 6d92762951a..452b1046cdf 100644 --- a/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx @@ -1,8 +1,8 @@ import type { ReactNode } from 'react' import type { PluginDetail } from '@/app/components/plugins/types' import { fireEvent, screen } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { PluginCategoryEnum, PluginSource } from '@/app/components/plugins/types' +import { renderWithConsoleQuery } from '@/test/console/query-data' import DataSourcePluginActions from '../plugin-actions' const { @@ -142,7 +142,7 @@ describe('DataSourcePluginActions', () => { it('opens the plugin README from the actions menu', () => { const detail = createPluginDetail() - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: true }, }) fireEvent.click( diff --git a/web/app/components/header/account-setting/members-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/members-page/__tests__/index.spec.tsx index b7f66eb2555..1af6135d86d 100644 --- a/web/app/components/header/account-setting/members-page/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/__tests__/index.spec.tsx @@ -1,56 +1,44 @@ import type { ReactElement } from 'react' -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { Role } from '@/models/access-control' import type { ICurrentWorkspace, Member } from '@/models/common' +import type { ConsoleStateFixture } from '@/test/console/state-fixture' import { screen, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { vi } from 'vitest' import { createMockProviderContextValue } from '@/__mocks__/provider-context' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { Plan } from '@/app/components/billing/type' import { useProviderContext } from '@/context/provider-context' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { useUpdateRolesOfMember } from '@/service/access-control/use-member-roles' import { useMembers } from '@/service/use-common' +import { renderWithConsoleQuery } from '@/test/console/query-data' import MembersPage from '../index' -const mockAppContextState = vi.hoisted(() => ({ - current: {} as Partial, +const mockConsoleState = vi.hoisted(() => ({ + current: {} as Partial, })) -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current) }) + vi.mock('@/context/provider-context') vi.mock('@/hooks/use-format-time-from-now') vi.mock('@/service/access-control/use-member-roles') vi.mock('@/service/use-common') const renderMembersPage = () => - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { is_email_setup: true }, }) @@ -72,9 +60,9 @@ const createRole = (overrides: Partial): Role => ({ ...overrides, }) -const setAppContextValue = (value: AppContextStateMockState) => { - mockAppContextState.current = value - mockUseAppContext.mockReturnValue(value) +const setConsoleState = (value: ConsoleStateFixture) => { + mockConsoleState.current = value + mockConsoleStateReader.mockReturnValue(value) } vi.mock('../edit-workspace-modal', () => ({ @@ -237,13 +225,13 @@ describe('MembersPage', () => { beforeEach(() => { vi.clearAllMocks() - setAppContextValue({ + setConsoleState({ userProfile: { email: 'owner@example.com' }, currentWorkspace: { name: 'Test Workspace', role: 'owner' } as ICurrentWorkspace, isCurrentWorkspaceOwner: true, isCurrentWorkspaceManager: true, workspacePermissionKeys: ['workspace.member.manage'], - } as unknown as AppContextStateMockState) + } as unknown as ConsoleStateFixture) vi.mocked(useMembers).mockReturnValue({ data: { accounts: mockAccounts }, @@ -291,7 +279,7 @@ describe('MembersPage', () => { }) it('should render plural roles column header when RBAC is enabled', () => { - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { is_email_setup: true, rbac_enabled: true, @@ -356,12 +344,12 @@ describe('MembersPage', () => { }) it('should hide manager controls for non-owner non-manager users', () => { - setAppContextValue({ + setConsoleState({ userProfile: { email: 'admin@example.com' }, currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace, isCurrentWorkspaceOwner: false, isCurrentWorkspaceManager: false, - } as unknown as AppContextStateMockState) + } as unknown as ConsoleStateFixture) renderMembersPage() @@ -469,13 +457,13 @@ describe('MembersPage', () => { }) it('should show invite button when user is manager but not owner', () => { - setAppContextValue({ + setConsoleState({ userProfile: { email: 'admin@example.com' }, currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace, isCurrentWorkspaceOwner: false, isCurrentWorkspaceManager: true, workspacePermissionKeys: ['workspace.member.manage'], - } as unknown as AppContextStateMockState) + } as unknown as ConsoleStateFixture) renderMembersPage() @@ -484,13 +472,13 @@ describe('MembersPage', () => { }) it('should allow admins to operate other non-owner members only', () => { - setAppContextValue({ + setConsoleState({ userProfile: { email: 'admin@example.com' }, currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace, isCurrentWorkspaceOwner: false, isCurrentWorkspaceManager: true, workspacePermissionKeys: ['workspace.member.manage'], - } as unknown as AppContextStateMockState) + } as unknown as ConsoleStateFixture) vi.mocked(useMembers).mockReturnValue({ data: { accounts: [ @@ -590,12 +578,12 @@ describe('MembersPage', () => { }) it('should render role badge names from account roles', () => { - setAppContextValue({ + setConsoleState({ userProfile: { email: 'admin@example.com' }, currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace, isCurrentWorkspaceOwner: false, isCurrentWorkspaceManager: false, - } as unknown as AppContextStateMockState) + } as unknown as ConsoleStateFixture) vi.mocked(useMembers).mockReturnValue({ data: { accounts: [{ ...mockAccounts[1], role: 'unknown_role' as Member['role'] }] }, refetch: mockRefetch, @@ -661,13 +649,13 @@ describe('MembersPage', () => { it('should not allow assigning roles from member details when target is current user', async () => { const user = userEvent.setup() - setAppContextValue({ + setConsoleState({ userProfile: { email: 'admin@example.com' }, currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace, isCurrentWorkspaceOwner: false, isCurrentWorkspaceManager: true, workspacePermissionKeys: ['workspace.member.manage'], - } as unknown as AppContextStateMockState) + } as unknown as ConsoleStateFixture) renderMembersPage() @@ -699,7 +687,7 @@ describe('MembersPage', () => { it('should submit multiple member roles when RBAC is enabled', async () => { const user = userEvent.setup() - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { is_email_setup: true, rbac_enabled: true, diff --git a/web/app/components/header/account-setting/members-page/__tests__/invite-button.spec.tsx b/web/app/components/header/account-setting/members-page/__tests__/invite-button.spec.tsx index 72683cb6ce1..2b42dd28f48 100644 --- a/web/app/components/header/account-setting/members-page/__tests__/invite-button.spec.tsx +++ b/web/app/components/header/account-setting/members-page/__tests__/invite-button.spec.tsx @@ -1,48 +1,20 @@ -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { ICurrentWorkspace } from '@/models/common' +import type { ConsoleStateFixture } from '@/test/console/state-fixture' import { screen } from '@testing-library/react' import { vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useWorkspacePermissions } from '@/service/use-workspace' +import { renderWithConsoleQuery } from '@/test/console/query-data' import InviteButton from '../invite-button' -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ currentWorkspace: { id: 'workspace-id' }, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-id' }, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-id' }, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-id' }, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-id' }, - })) -}) -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) + vi.mock('@/service/use-workspace') describe('InviteButton', () => { @@ -60,15 +32,15 @@ describe('InviteButton', () => { } const renderInviteButton = (brandingEnabled: boolean) => - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { branding: { enabled: brandingEnabled } }, }) beforeEach(() => { vi.clearAllMocks() - mockUseAppContext.mockReturnValue({ + mockConsoleStateReader.mockReturnValue({ currentWorkspace: { id: 'workspace-id' } as ICurrentWorkspace, - } as unknown as AppContextStateMockState) + } as unknown as ConsoleStateFixture) }) it('should show invite button when branding is disabled', () => { diff --git a/web/app/components/header/account-setting/members-page/__tests__/member-menu.spec.tsx b/web/app/components/header/account-setting/members-page/__tests__/member-menu.spec.tsx index b0160dc7530..98c835987a9 100644 --- a/web/app/components/header/account-setting/members-page/__tests__/member-menu.spec.tsx +++ b/web/app/components/header/account-setting/members-page/__tests__/member-menu.spec.tsx @@ -4,11 +4,11 @@ import { toast } from '@langgenius/dify-ui/toast' import { QueryClient } from '@tanstack/react-query' import { screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useUpdateRolesOfMember } from '@/service/access-control/use-member-roles' import { useWorkspaceRoleList } from '@/service/access-control/use-workspace-roles' import { deleteMemberOrCancelInvitation } from '@/service/common' import { commonQueryKeys } from '@/service/use-common' +import { renderWithConsoleQuery } from '@/test/console/query-data' import MemberMenu from '../member-menu' vi.mock('@/service/access-control/use-member-roles') @@ -103,7 +103,7 @@ describe('MemberMenu', () => { it('should show edit role copy when multiple roles are disabled', async () => { const user = userEvent.setup() - renderWithSystemFeatures( + renderWithConsoleQuery( , { systemFeatures: { @@ -127,7 +127,7 @@ describe('MemberMenu', () => { it('should submit only one selected role from the assign modal when RBAC is disabled', async () => { const user = userEvent.setup() - renderWithSystemFeatures( + renderWithConsoleQuery( , { systemFeatures: { @@ -156,7 +156,7 @@ describe('MemberMenu', () => { const membersQueryKey = [...commonQueryKeys.members, 'en-US'] queryClient.setQueryData(membersQueryKey, { accounts: [member] }) - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { queryClient, }) diff --git a/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx b/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx index b36f3734b2d..9e8af9baa62 100644 --- a/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx @@ -1,46 +1,27 @@ -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { ICurrentWorkspace } from '@/models/common' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import type { ConsoleStateFixture } from '@/test/console/state-fixture' +import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { vi } from 'vitest' import { updateWorkspaceInfo } from '@/service/common' +import { render } from '@/test/console/render' import EditWorkspaceModal from '../index' const toastMocks = vi.hoisted(() => ({ mockNotify: vi.fn(), })) -const mockAppContextState = vi.hoisted(() => ({ - current: {} as Partial, +const mockConsoleState = vi.hoisted(() => ({ + current: {} as Partial, })) -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) const getSaveButton = () => screen.getByRole('button', { name: /operation\.(save|saving)/i }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState.current) }) + vi.mock('@/service/common') vi.mock('@langgenius/dify-ui/toast', () => ({ default: { @@ -61,12 +42,12 @@ describe('EditWorkspaceModal', () => { beforeEach(() => { vi.clearAllMocks() - const appContextValue = { + const consoleState = { currentWorkspace: { name: 'Test Workspace' } as ICurrentWorkspace, isCurrentWorkspaceOwner: true, - } as unknown as AppContextStateMockState - mockAppContextState.current = appContextValue - mockUseAppContext.mockReturnValue(appContextValue) + } as unknown as ConsoleStateFixture + mockConsoleState.current = consoleState + mockConsoleStateReader.mockReturnValue(consoleState) }) afterEach(() => { @@ -188,10 +169,10 @@ describe('EditWorkspaceModal', () => { }) it('should disable confirm button for non-owners', async () => { - mockUseAppContext.mockReturnValue({ + mockConsoleStateReader.mockReturnValue({ currentWorkspace: { name: 'Test Workspace' } as ICurrentWorkspace, isCurrentWorkspaceOwner: false, - } as unknown as AppContextStateMockState) + } as unknown as ConsoleStateFixture) renderModal() diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx index a9e81be7234..f3193e7e1cf 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx @@ -1,45 +1,30 @@ -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { ICurrentWorkspace } from '@/models/common' -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import type { ConsoleStateFixture } from '@/test/console/state-fixture' +import { act, fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { vi } from 'vitest' import { ownershipTransfer, sendOwnerEmail, verifyOwnerEmail } from '@/service/common' import { useMembers } from '@/service/use-common' +import { render } from '@/test/console/render' import TransferOwnershipModal from '../index' const toastMocks = vi.hoisted(() => ({ mockNotify: vi.fn(), })) -const mockAppContextState = vi.hoisted(() => ({ - current: {} as Partial, +const mockConsoleState = vi.hoisted(() => ({ + current: {} as Partial, })) -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState.current) }) + vi.mock('@/service/common') vi.mock('@/service/use-common') vi.mock('@langgenius/dify-ui/toast', () => ({ @@ -67,12 +52,12 @@ describe('TransferOwnershipModal', () => { beforeEach(() => { vi.clearAllMocks() - const appContextValue = { + const consoleState = { currentWorkspace: { name: 'Test Workspace' } as ICurrentWorkspace, userProfile: { email: 'owner@example.com', id: 'owner-id' }, - } as unknown as AppContextStateMockState - mockAppContextState.current = appContextValue - mockUseAppContext.mockReturnValue(appContextValue) + } as unknown as ConsoleStateFixture + mockConsoleState.current = consoleState + mockConsoleStateReader.mockReturnValue(consoleState) vi.mocked(useMembers).mockReturnValue({ data: { accounts: [] }, diff --git a/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx b/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx index 78c1ebd0c6f..5b391937808 100644 --- a/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx @@ -1,5 +1,5 @@ import { screen } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { CurrentSystemQuotaTypeEnum, CustomConfigurationStatusEnum, @@ -179,7 +179,7 @@ vi.mock('@/service/client', async (importOriginal) => { describe('ModelProviderPage non-cloud branch', () => { it('should skip the quota panel when cloud edition is disabled', () => { - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { enable_marketplace: false }, }) diff --git a/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx index b5b8165c8ed..170ff4191d6 100644 --- a/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx @@ -2,8 +2,8 @@ import type { ReactNode } from 'react' import type { PluginDeclaration, PluginDetail } from '@/app/components/plugins/types' import { act, fireEvent, screen } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { PluginCategoryEnum, PluginSource } from '@/app/components/plugins/types' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { CurrentSystemQuotaTypeEnum, CustomConfigurationStatusEnum, @@ -74,7 +74,7 @@ const renderModelProviderPage = ( } = {}, ) => { const { searchText = '', enableMarketplace = true, stickyToolbar = true } = props - return renderWithSystemFeatures( + return renderWithConsoleQuery( , { systemFeatures: { enable_marketplace: enableMarketplace }, diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-credential-in-load-balancing.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-credential-in-load-balancing.spec.tsx index 08d0e54241a..2ea252c914d 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-credential-in-load-balancing.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-credential-in-load-balancing.spec.tsx @@ -3,53 +3,24 @@ import type { ModelCredential, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { ConfigurationMethodEnum, ModelTypeEnum, } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { render } from '@/test/console/render' import AddCredentialInLoadBalancing from '../add-credential-in-load-balancing' const mockWorkspacePermissionKeys = vi.hoisted(() => ({ value: ['credential.use', 'credential.create', 'credential.manage'], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/app/components/header/account-setting/model-provider-page/model-auth', () => ({ Authorized: ({ diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx index bbdef9031e1..d19db3d69f4 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx @@ -1,6 +1,7 @@ import type { ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { render } from '@/test/console/render' import AddCustomModel from '../add-custom-model' // Mock hooks @@ -33,42 +34,12 @@ const mockWorkspacePermissionKeys = vi.hoisted(() => ({ value: ['credential.use', 'credential.create', 'credential.manage'], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) // Mock components vi.mock('../../model-icon', () => ({ diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/switch-credential-in-load-balancing.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/switch-credential-in-load-balancing.spec.tsx index 9c7f5736312..6f00a022d66 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/switch-credential-in-load-balancing.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/switch-credential-in-load-balancing.spec.tsx @@ -2,51 +2,22 @@ import type { CustomModel, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { render } from '@/test/console/render' import SwitchCredentialInLoadBalancing from '../switch-credential-in-load-balancing' const mockWorkspacePermissionKeys = vi.hoisted(() => ({ value: ['credential.use', 'credential.create', 'credential.manage'], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) // Mock components vi.mock('../authorized', () => ({ diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/index.spec.tsx index 7eb857c3e83..7db7c373fd4 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/index.spec.tsx @@ -1,5 +1,6 @@ import type { Credential, CustomModel, ModelProvider } from '../../../declarations' -import { fireEvent, render, screen, within } from '@testing-library/react' +import { fireEvent, screen, within } from '@testing-library/react' +import { render } from '@/test/console/render' import { ConfigurationMethodEnum, ModelTypeEnum } from '../../../declarations' import Authorized from '../index' @@ -13,42 +14,12 @@ let mockDeleteCredentialId: string | null = null let mockDoingAction = false let mockWorkspacePermissionKeys = ['credential.use', 'credential.create', 'credential.manage'] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('../../hooks', () => ({ useAuth: () => ({ diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/__tests__/dialog.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/__tests__/dialog.spec.tsx index 4968a7fc88e..63320ef0f76 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/__tests__/dialog.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/__tests__/dialog.spec.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from 'react' import type { Credential, ModelProvider } from '../../declarations' -import { act, render, screen } from '@testing-library/react' +import { act, screen } from '@testing-library/react' +import { render } from '@/test/console/render' import { ConfigurationMethodEnum, ModelModalModeEnum } from '../../declarations' import ModelModal from '../index' @@ -23,6 +24,13 @@ let mockDeleteCredentialId: string | null = null const mockCloseConfirmDelete = vi.fn() const mockHandleConfirmDelete = vi.fn() +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/app/components/base/form/form-scenarios/auth', () => ({ default: () =>
, })) @@ -133,6 +141,14 @@ const createProvider = (overrides: Partial = {}): ModelProvider = ...overrides, }) as unknown as ModelProvider +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('ModelModal dialog branches', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/__tests__/index.spec.tsx index edffedbcf0b..5278c4b059c 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/__tests__/index.spec.tsx @@ -1,7 +1,8 @@ import type * as React from 'react' import type { Credential, CredentialFormSchema, ModelProvider } from '../../declarations' import type { FormSchema } from '@/app/components/base/form/types' -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import { fireEvent, screen, waitFor, within } from '@testing-library/react' +import { render } from '@/test/console/render' import { ConfigurationMethodEnum, CurrentSystemQuotaTypeEnum, @@ -76,42 +77,12 @@ vi.mock('../../model-auth/hooks', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockState.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockState.workspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockState.workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockState.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockState.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/hooks/use-i18n', () => ({ useRenderI18nObject: () => (value: { en_US: string }) => value.en_US, diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx index f015512a434..1c03fdd5a40 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx @@ -71,7 +71,7 @@ const makeModel = (overrides: Partial = {}): Model => ({ ...overrides, }) -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false }, @@ -80,7 +80,7 @@ const createTestQueryClient = () => }) const renderWithQueryClient = (node: ReactNode) => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() return render({node}) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup-item.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup-item.spec.tsx index 91837f94771..71a1c0aebf0 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup-item.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup-item.spec.tsx @@ -2,8 +2,9 @@ import type { ReactElement, ReactNode } from 'react' import type { DefaultModel, Model, ModelItem } from '../../declarations' import { Combobox } from '@langgenius/dify-ui/combobox' import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import { ConfigurationMethodEnum, CustomConfigurationStatusEnum, @@ -87,47 +88,23 @@ vi.mock('@/context/provider-context', () => ({ useProviderContext: mockUseProviderContext, })) -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) const mockWorkspacePermissionKeys = vi.hoisted(() => ({ value: ['credential.use', 'credential.create', 'credential.manage'], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) const makeModelItem = (overrides: Partial = {}): ModelItem => ({ model: 'gpt-4', @@ -183,7 +160,7 @@ describe('PopupItem', () => { mockUseProviderContext.mockReturnValue({ modelProviders: [makeProvider()], }) - mockUseAppContext.mockReturnValue({ + mockConsoleStateReader.mockReturnValue({ currentWorkspace: { trial_credits: 200, trial_credits_used: 0 }, }) mockCredentialPanelState.mockReturnValue({ @@ -496,7 +473,7 @@ describe('PopupItem', () => { }), ], }) - mockUseAppContext.mockReturnValue({ + mockConsoleStateReader.mockReturnValue({ currentWorkspace: { trial_credits: 100, trial_credits_used: 100 }, }) mockCredentialPanelState.mockReturnValue({ diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup.spec.tsx index 4a0edcd4f88..311daddd960 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup.spec.tsx @@ -5,7 +5,7 @@ import { Combobox } from '@langgenius/dify-ui/combobox' import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useState } from 'react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { ConfigurationMethodEnum, ModelFeatureEnum, @@ -115,9 +115,9 @@ function PopupHarness(props: PopupTestProps) { const renderPopup = ( ui: ReactElement, - options: Parameters[1] = {}, + options: Parameters[1] = {}, ) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { ...options, systemFeatures: options.systemFeatures === null diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/credential-panel.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/credential-panel.spec.tsx index 1bec6ece763..63df6ae6d0c 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/credential-panel.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/credential-panel.spec.tsx @@ -1,6 +1,6 @@ import type { ModelProvider } from '../../declarations' import { act, fireEvent, screen, waitFor } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { ConfigurationMethodEnum, CurrentSystemQuotaTypeEnum, @@ -140,7 +140,7 @@ const createProvider = (overrides: Partial = {}): ModelProvider = }) as unknown as ModelProvider const renderWithQueryClient = (provider: ModelProvider) => { - return renderWithSystemFeatures(, { + return renderWithConsoleQuery(, { trialModels: ['langgenius/openai/openai'], }) } diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/index.spec.tsx index acd2ee41605..8f91f8a5ac8 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/index.spec.tsx @@ -1,8 +1,10 @@ import type { ReactNode } from 'react' import type { ModelProvider } from '../../declarations' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, screen, waitFor } from '@testing-library/react' import { createStore, Provider as JotaiProvider } from 'jotai' +import { render } from '@/test/console/render' +import { seedRegisteredConsoleStateFixture } from '@/test/console/state-fixture' import { useExpandModelProviderList } from '../../atoms' import { ConfigurationMethodEnum } from '../../declarations' import ProviderAddedCard from '../index' @@ -44,47 +46,20 @@ vi.mock('@/service/client', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager, workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) // Mock internal components to simplify testing of the index file vi.mock('../credential-panel', () => ({ @@ -123,7 +98,7 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-auth' ManageCustomModelCredentials: () =>
, })) -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 }, @@ -131,8 +106,9 @@ const createTestQueryClient = () => }) const renderWithQueryClient = (node: ReactNode) => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() const store = createStore() + seedRegisteredConsoleStateFixture(store) return render( {node} diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx index 5ead6cc60ad..5fb62233f84 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx @@ -1,7 +1,8 @@ import type { ModelItem, ModelProvider } from '../../declarations' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { disableModel, enableModel } from '@/service/common' +import { render } from '@/test/console/render' import { ModelStatusEnum } from '../../declarations' import ModelListItem from '../model-list-item' @@ -16,42 +17,12 @@ let mockModelLoadBalancingEnabled = false let mockPlanType: string = 'pro' let mockWorkspacePermissionKeys: string[] = ['plugin.model_config'] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/provider-context', () => ({ useProviderContext: () => ({ diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx index dc7178b4186..c838ce63cfa 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx @@ -1,5 +1,6 @@ import type { ModelItem, ModelProvider } from '../../declarations' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' +import { render } from '@/test/console/render' import { ConfigurationMethodEnum } from '../../declarations' import ModelList from '../model-list' @@ -10,42 +11,12 @@ let mockWorkspacePermissionKeys: string[] = [ 'credential.use', ] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/modal-context', () => ({ useModalContextSelector: ( diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx index b75a47315a0..1266c7d9058 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx @@ -1,8 +1,8 @@ import type { ReactElement, ReactNode } from 'react' import type { PluginDetail } from '@/app/components/plugins/types' import { fireEvent, screen } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { PluginSource } from '@/app/components/plugins/types' +import { renderWithConsoleQuery } from '@/test/console/query-data' import ProviderCardActions from '../provider-card-actions' const mockHandleUpdate = vi.fn() @@ -33,7 +33,7 @@ let mockHeaderState = { } const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { systemFeatures: { enable_marketplace: true } }) + renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: true } }) const openActionsMenu = () => { fireEvent.click(screen.getByRole('button', { name: 'plugin.detailPanel.operation.moreActions' })) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/quota-panel.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/quota-panel.spec.tsx index ed687222a61..5ebf3cf68c4 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/quota-panel.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/quota-panel.spec.tsx @@ -1,7 +1,7 @@ import type { ReactElement } from 'react' import type { ModelProvider } from '../../declarations' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import QuotaPanel from '../quota-panel' let mockWorkspaceData: @@ -57,7 +57,7 @@ vi.mock('../use-trial-credits', () => ({ })) const renderQuotaPanel = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { trialModels: mockTrialModels ?? [], }) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/use-change-provider-priority.spec.ts b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/use-change-provider-priority.spec.ts index 14b72b399cb..283b6e516fe 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/use-change-provider-priority.spec.ts +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/use-change-provider-priority.spec.ts @@ -86,7 +86,7 @@ const createProvider = (overrides: Partial = {}): ModelProvider = ...overrides, }) as ModelProvider -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 }, @@ -107,7 +107,7 @@ describe('useChangeProviderPriority', () => { describe('when changing provider priority', () => { it('should submit the selected preferred provider type for the current provider', async () => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() const invalidateQueries = vi .spyOn(queryClient, 'invalidateQueries') .mockResolvedValue(undefined) @@ -152,7 +152,7 @@ describe('useChangeProviderPriority', () => { }) it('should tolerate an undefined provider and still submit a request without refreshing model lists', async () => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() const invalidateQueries = vi .spyOn(queryClient, 'invalidateQueries') .mockResolvedValue(undefined) @@ -183,7 +183,7 @@ describe('useChangeProviderPriority', () => { describe('when the mutation is not successful immediately', () => { it('should show an error toast when the mutation fails', async () => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() const invalidateQueries = vi .spyOn(queryClient, 'invalidateQueries') .mockResolvedValue(undefined) @@ -218,7 +218,7 @@ describe('useChangeProviderPriority', () => { }), ) - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() const { result } = renderHook(() => useChangeProviderPriority(createProvider()), { wrapper: createWrapper(queryClient), }) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/use-credential-panel-state.spec.ts b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/use-credential-panel-state.spec.ts index 95866013ba0..90809502cf7 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/use-credential-panel-state.spec.ts +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/use-credential-panel-state.spec.ts @@ -1,5 +1,5 @@ import type { ModelProvider } from '../../declarations' -import { renderHookWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderHookWithConsoleQuery } from '@/test/console/query-data' import { ConfigurationMethodEnum, CurrentSystemQuotaTypeEnum, @@ -28,7 +28,7 @@ vi.mock('@/config', async (importOriginal) => { const renderPanelHook = (provider: ModelProvider | undefined) => { // oxlint-disable-next-line eslint-react/use-state -- This is a domain hook, not React's useState. - return renderHookWithSystemFeatures(() => useCredentialPanelState(provider), { + return renderHookWithConsoleQuery(() => useCredentialPanelState(provider), { trialModels: mockTrialModels, }) } diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/api-key-section.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/api-key-section.spec.tsx index b3dc4e1b955..aa1752b75f2 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/api-key-section.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/api-key-section.spec.tsx @@ -1,44 +1,15 @@ import type { Credential, ModelProvider } from '../../../declarations' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' +import { render } from '@/test/console/render' import { CustomConfigurationStatusEnum, PreferredProviderTypeEnum } from '../../../declarations' import ApiKeySection from '../api-key-section' -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) const createCredential = (overrides: Partial = {}): Credential => ({ credential_id: 'cred-1', diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/dialog.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/dialog.spec.tsx index 95dfc9fca18..b2e34d50897 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/dialog.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/dialog.spec.tsx @@ -1,7 +1,8 @@ import type { ReactNode } from 'react' import type { ModelProvider } from '../../../declarations' import type { CredentialPanelState } from '../../use-credential-panel-state' -import { act, fireEvent, render, screen } from '@testing-library/react' +import { act, fireEvent, screen } from '@testing-library/react' +import { render } from '@/test/console/render' import DropdownContent from '../dropdown-content' type AlertDialogProps = { @@ -15,6 +16,13 @@ const mockCloseConfirmDelete = vi.fn() const mockHandleConfirmDelete = vi.fn() const mockHandleOpenModal = vi.fn() +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('../../../model-auth/hooks', () => ({ useAuth: () => ({ openConfirmDelete: mockOpenConfirmDelete, @@ -116,6 +124,14 @@ const createState = (overrides: Partial = {}): CredentialP ...overrides, }) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('DropdownContent dialog branches', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/dropdown-content.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/dropdown-content.spec.tsx index 618b11a848d..94b1730d085 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/dropdown-content.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/dropdown-content.spec.tsx @@ -1,6 +1,7 @@ import type { ModelProvider } from '../../../declarations' import type { CredentialPanelState } from '../../use-credential-panel-state' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' +import { render } from '@/test/console/render' import { CustomConfigurationStatusEnum, PreferredProviderTypeEnum } from '../../../declarations' import DropdownContent from '../dropdown-content' @@ -40,42 +41,12 @@ vi.mock('../../../model-auth/hooks', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('../../../model-auth/authorized/credential-item', () => ({ default: ({ diff --git a/web/app/components/header/account-setting/model-provider-page/system-model-selector/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/system-model-selector/__tests__/index.spec.tsx index 79c3917c97a..370fb17158b 100644 --- a/web/app/components/header/account-setting/model-provider-page/system-model-selector/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/system-model-selector/__tests__/index.spec.tsx @@ -1,6 +1,7 @@ import type { DefaultModelResponse } from '../../declarations' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { vi } from 'vitest' +import { render } from '@/test/console/render' import { ModelTypeEnum } from '../../declarations' import SystemModel from '../index' @@ -41,42 +42,12 @@ const mockModelSelectorProps = vi.hoisted( let mockWorkspacePermissionKeys = ['plugin.model_config'] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/provider-context', () => ({ useProviderContext: () => ({ diff --git a/web/app/components/header/account-setting/permissions-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/permissions-page/__tests__/index.spec.tsx index 7cbda04da50..7f9ea498088 100644 --- a/web/app/components/header/account-setting/permissions-page/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/permissions-page/__tests__/index.spec.tsx @@ -1,11 +1,12 @@ import type { Role } from '@/models/access-control' import { toast } from '@langgenius/dify-ui/toast' -import { render, screen, within } from '@testing-library/react' +import { screen, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useCreateWorkspaceRole, useUpdateWorkspaceRole, } from '@/service/access-control/use-workspace-roles' +import { render } from '@/test/console/render' import { useRoleGroups } from '../hooks' import PermissionsPage from '../index' @@ -21,42 +22,12 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mocks.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mocks.workspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mocks.workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mocks.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mocks.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/access-control/use-workspace-roles', () => ({ useCreateWorkspaceRole: vi.fn(), diff --git a/web/app/components/header/account-setting/permissions-page/role-list/__tests__/index.spec.tsx b/web/app/components/header/account-setting/permissions-page/role-list/__tests__/index.spec.tsx index 89ded339a7c..c5bded8f322 100644 --- a/web/app/components/header/account-setting/permissions-page/role-list/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/permissions-page/role-list/__tests__/index.spec.tsx @@ -1,11 +1,19 @@ import type { Role } from '@/models/access-control' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' +import { render } from '@/test/console/render' import RoleList from '../index' const mockWorkspacePermissionKeys = vi.hoisted(() => ({ value: ['workspace.role.manage'] as string[], })) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/service/access-control/use-workspace-roles', () => ({ useCopyWorkspaceRole: () => ({ mutateAsync: vi.fn(), @@ -41,6 +49,14 @@ const createRole = (overrides: Partial = {}): Role => ({ ...overrides, }) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('RoleList', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/header/account-setting/permissions-page/role-list/__tests__/row-menu.spec.tsx b/web/app/components/header/account-setting/permissions-page/role-list/__tests__/row-menu.spec.tsx index 6597024aa6b..926547483f7 100644 --- a/web/app/components/header/account-setting/permissions-page/role-list/__tests__/row-menu.spec.tsx +++ b/web/app/components/header/account-setting/permissions-page/role-list/__tests__/row-menu.spec.tsx @@ -1,6 +1,7 @@ import type { Role } from '@/models/access-control' -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import { fireEvent, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import RowMenu from '../row-menu' const mockWorkspacePermissionKeys = vi.hoisted(() => ({ @@ -32,42 +33,12 @@ const membersOfRoleQueryMock = vi.hoisted(() => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/access-control/use-workspace-roles', () => ({ useCopyWorkspaceRole: () => ({ diff --git a/web/app/components/header/account-setting/preference-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/preference-page/__tests__/index.spec.tsx index 7c58550bdc9..d6095ac19a2 100644 --- a/web/app/components/header/account-setting/preference-page/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/preference-page/__tests__/index.spec.tsx @@ -1,8 +1,9 @@ import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen' import { ToastHost } from '@langgenius/dify-ui/toast' -import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import { act, fireEvent, screen, waitFor, within } from '@testing-library/react' import { languages } from '@/i18n-config/language' import { updateUserProfile } from '@/service/common' +import { render } from '@/test/console/render' import { timezones } from '@/utils/timezone' import PreferencePage from '../index' @@ -81,47 +82,13 @@ vi.mock('@/next/navigation', () => ({ useRouter: () => ({ refresh: mockRefresh }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: mockUserProfile, refreshUserProfile: mockMutateUserProfile, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - refreshUserProfile: mockMutateUserProfile, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - refreshUserProfile: mockMutateUserProfile, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - refreshUserProfile: mockMutateUserProfile, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - refreshUserProfile: mockMutateUserProfile, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/i18n', () => ({ useLocale: () => mockLocale, diff --git a/web/app/components/header/account-setting/workflow-log-archives-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/workflow-log-archives-page/__tests__/index.spec.tsx index b5d49cb033a..9fbae99cb82 100644 --- a/web/app/components/header/account-setting/workflow-log-archives-page/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/workflow-log-archives-page/__tests__/index.spec.tsx @@ -2,15 +2,12 @@ import type { GetWorkflowRunArchivesResponse } from '@dify/contracts/api/console import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createMockProviderContextValue } from '@/__mocks__/provider-context' -import { - createTestQueryClient, - renderWithSystemFeatures, -} from '@/__tests__/utils/mock-system-features' import { defaultPlan } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' import { consoleQuery } from '@/service/client' +import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data' import WorkflowLogArchivesPage from '../index' vi.mock('@/config', async (importOriginal) => { @@ -74,10 +71,10 @@ function mockPlan(planType: Plan.sandbox | Plan.professional) { } function renderPage() { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() queryClient.setQueryData(consoleQuery.workflowRunArchives.get.queryKey(), archiveData) - return renderWithSystemFeatures(, { + return renderWithConsoleQuery(, { queryClient, }) } diff --git a/web/app/components/header/env-nav/__tests__/index.spec.tsx b/web/app/components/header/env-nav/__tests__/index.spec.tsx index feeb1b201ff..ae58d3de8ba 100644 --- a/web/app/components/header/env-nav/__tests__/index.spec.tsx +++ b/web/app/components/header/env-nav/__tests__/index.spec.tsx @@ -1,38 +1,17 @@ -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' -import { render, screen } from '@testing-library/react' +import type { ConsoleStateFixture } from '@/test/console/state-fixture' +import { screen } from '@testing-library/react' import { vi } from 'vitest' +import { render } from '@/test/console/render' import EnvNav from '../index' -const mockAppContextState = vi.hoisted(() => ({ - current: {} as Partial, +const mockConsoleState = vi.hoisted(() => ({ + current: {} as Partial, })) -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState.current) }) describe('EnvNav', () => { @@ -41,39 +20,39 @@ describe('EnvNav', () => { }) it('should render null when environment is PRODUCTION', () => { - const appContextValue = { + const consoleState = { langGeniusVersionInfo: { current_env: 'PRODUCTION', }, - } as unknown as AppContextStateMockState - mockAppContextState.current = appContextValue - mockUseAppContext.mockReturnValue(appContextValue) + } as unknown as ConsoleStateFixture + mockConsoleState.current = consoleState + mockConsoleStateReader.mockReturnValue(consoleState) const { container } = render() expect(container.firstChild).toBeNull() }) it('should render TESTING tag and icon when environment is TESTING', () => { - const appContextValue = { + const consoleState = { langGeniusVersionInfo: { current_env: 'TESTING', }, - } as unknown as AppContextStateMockState - mockAppContextState.current = appContextValue - mockUseAppContext.mockReturnValue(appContextValue) + } as unknown as ConsoleStateFixture + mockConsoleState.current = consoleState + mockConsoleStateReader.mockReturnValue(consoleState) render() expect(screen.getByText('common.environment.testing')).toBeInTheDocument() }) it('should render DEVELOPMENT tag and icon when environment is DEVELOPMENT', () => { - const appContextValue = { + const consoleState = { langGeniusVersionInfo: { current_env: 'DEVELOPMENT', }, - } as unknown as AppContextStateMockState - mockAppContextState.current = appContextValue - mockUseAppContext.mockReturnValue(appContextValue) + } as unknown as ConsoleStateFixture + mockConsoleState.current = consoleState + mockConsoleStateReader.mockReturnValue(consoleState) render() expect(screen.getByText('common.environment.development')).toBeInTheDocument() diff --git a/web/app/components/header/license-env/__tests__/index.spec.tsx b/web/app/components/header/license-env/__tests__/index.spec.tsx index fc3d1e95527..ad3089d5814 100644 --- a/web/app/components/header/license-env/__tests__/index.spec.tsx +++ b/web/app/components/header/license-env/__tests__/index.spec.tsx @@ -1,7 +1,7 @@ import { screen } from '@testing-library/react' import dayjs from 'dayjs' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { LicenseStatus } from '@/features/system-features/constants' +import { renderWithConsoleQuery } from '@/test/console/query-data' import LicenseNav from '../index' describe('LicenseNav', () => { @@ -17,12 +17,12 @@ describe('LicenseNav', () => { }) it('should render null when license status is NONE', () => { - const { container } = renderWithSystemFeatures() + const { container } = renderWithConsoleQuery() expect(container).toBeEmptyDOMElement() }) it('should render Enterprise badge when license status is ACTIVE', () => { - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { license: { status: LicenseStatus.ACTIVE, @@ -35,7 +35,7 @@ describe('LicenseNav', () => { it('should render singular expiring message when license expires in 0 days', () => { const expiredAt = dayjs().add(2, 'hours').toISOString() - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { license: { status: LicenseStatus.EXPIRING, @@ -49,7 +49,7 @@ describe('LicenseNav', () => { it('should render singular expiring message when license expires in 1 day', () => { const tomorrow = dayjs().add(1, 'day').add(1, 'hour').toISOString() - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { license: { status: LicenseStatus.EXPIRING, @@ -63,7 +63,7 @@ describe('LicenseNav', () => { it('should render plural expiring message when license expires in 5 days', () => { const fiveDaysLater = dayjs().add(5, 'day').add(1, 'hour').toISOString() - renderWithSystemFeatures(, { + renderWithConsoleQuery(, { systemFeatures: { license: { status: LicenseStatus.EXPIRING, diff --git a/web/app/components/integrations/__tests__/page.spec.tsx b/web/app/components/integrations/__tests__/page.spec.tsx index 72c98517f75..128056f7e90 100644 --- a/web/app/components/integrations/__tests__/page.spec.tsx +++ b/web/app/components/integrations/__tests__/page.spec.tsx @@ -8,7 +8,7 @@ const { mockRouterPush, mockWindowOpen } = vi.hoisted(() => ({ mockWindowOpen: vi.fn(), })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['tool.manage', 'mcp.manage'] as string[], })) @@ -96,11 +96,9 @@ vi.mock('@/app/components/plugins/reference-setting-modal', () => ({ })) vi.mock('@/app/components/header/account-setting/update-setting-dialog', () => ({ - __esModule: true, default: () => ( - ), })) @@ -319,7 +317,7 @@ describe('IntegrationsPage', () => { mockCanManagement.mockReturnValue(true) mockCanDebugger.mockReturnValue(true) mockCanSetPermissions.mockReturnValue(true) - mockAppContextState.workspacePermissionKeys = ['tool.manage', 'mcp.manage'] + mockConsoleState.workspacePermissionKeys = ['tool.manage', 'mcp.manage'] mockReferenceSetting.mockReturnValue({ permission: { install_permission: 'everyone', @@ -555,7 +553,7 @@ describe('IntegrationsPage', () => { }) it('renders the MCP route as read-only without mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = ['tool.manage'] + mockConsoleState.workspacePermissionKeys = ['tool.manage'] renderIntegrationsPage(undefined, 'mcp') @@ -565,7 +563,7 @@ describe('IntegrationsPage', () => { it.each(['custom-tool', 'workflow-tool'] as const)( 'renders the %s route as read-only without tool.manage', (section) => { - mockAppContextState.workspacePermissionKeys = ['mcp.manage'] + mockConsoleState.workspacePermissionKeys = ['mcp.manage'] renderIntegrationsPage(undefined, section) @@ -674,7 +672,7 @@ describe('IntegrationsPage', () => { it('keeps custom, workflow, and MCP tool entries visible without manage permissions', async () => { const user = userEvent.setup() - mockAppContextState.workspacePermissionKeys = ['mcp.manage'] + mockConsoleState.workspacePermissionKeys = ['mcp.manage'] renderIntegrationsPage(undefined, { section: 'provider', onSectionChange: vi.fn() }) await user.click(screen.getByRole('button', { name: 'common.menus.tools' })) @@ -889,8 +887,9 @@ describe('IntegrationsPage', () => { (section) => { renderIntegrationsPage({ section }) - expect(screen.getByText('plugin.autoUpdate.autoUpdate')).toBeInTheDocument() - expect(screen.getByText('plugin.autoUpdate.strategy.fixOnly.name')).toBeInTheDocument() + expect( + screen.getByRole('button', { name: 'plugin.autoUpdate.autoUpdate' }), + ).toBeInTheDocument() }, ) @@ -939,7 +938,9 @@ describe('IntegrationsPage', () => { renderIntegrationsPage({ section: 'trigger' }) - expect(screen.queryByTestId('update-setting-dialog')).not.toBeInTheDocument() + expect( + screen.queryByRole('button', { name: 'plugin.autoUpdate.autoUpdate' }), + ).not.toBeInTheDocument() }) it('opens the sidebar plugin permissions quick settings and updates permissions', async () => { diff --git a/web/app/components/integrations/__tests__/tool-provider-list.spec.tsx b/web/app/components/integrations/__tests__/tool-provider-list.spec.tsx index 317fc40d7fb..128b1dd9a3d 100644 --- a/web/app/components/integrations/__tests__/tool-provider-list.spec.tsx +++ b/web/app/components/integrations/__tests__/tool-provider-list.spec.tsx @@ -1,8 +1,8 @@ import type { ComponentProps, ReactNode } from 'react' import { cleanup, fireEvent, screen, within } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { createSystemFeaturesWrapper } from '@/__tests__/utils/mock-system-features' import { getToolType } from '@/app/components/tools/utils' +import { createConsoleQueryWrapper } from '@/test/console/query-data' import { renderWithNuqs } from '@/test/nuqs-testing' import { ToolType } from '../../workflow/block-selector/types' import ProviderList from '../tool-provider-list' @@ -94,46 +94,16 @@ vi.mock('@/service/use-tools', () => ({ useAllToolProviders: (enabled?: boolean) => mockUseAllToolProviders(enabled), })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['tool.manage', 'mcp.manage'] as string[], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContextState.workspacePermissionKeys, +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContextState.workspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContextState.workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContextState.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContextState.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) let mockCheckedInstalledData: { plugins: { id: string; name: string }[] } | null = null const mockInvalidateInstalledPluginList = vi.fn() @@ -184,14 +154,10 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({ })) vi.mock('@/app/components/header/account-setting/update-setting-dialog', () => ({ - __esModule: true, default: () => ( -
- -
+ ), })) @@ -386,11 +352,11 @@ const renderProviderList = ( category?: ComponentProps['category'], contentInset?: ComponentProps['contentInset'], ) => { - const { wrapper: SystemFeaturesWrapper } = createSystemFeaturesWrapper({ + const { wrapper: ConsoleQueryWrapper } = createConsoleQueryWrapper({ systemFeatures: { enable_marketplace: mockEnableMarketplace }, }) const Wrapped = ({ children }: { children: ReactNode }) => ( - {children} + {children} ) return renderWithNuqs( @@ -406,7 +372,7 @@ describe('ProviderList', () => { mockEnableMarketplace = false mockCollectionData = createDefaultCollections() mockIsLoadingToolProviders = false - mockAppContextState.workspacePermissionKeys = ['tool.manage', 'mcp.manage'] + mockConsoleState.workspacePermissionKeys = ['tool.manage', 'mcp.manage'] mockUseAllToolProviders.mockImplementation((enabled = true) => ({ data: enabled ? mockCollectionData : [], isLoading: enabled ? mockIsLoadingToolProviders : false, @@ -444,7 +410,7 @@ describe('ProviderList', () => { }) it('keeps custom and workflow tabs visible without tool.manage', () => { - mockAppContextState.workspacePermissionKeys = ['mcp.manage'] + mockConsoleState.workspacePermissionKeys = ['mcp.manage'] renderProviderList() @@ -455,7 +421,7 @@ describe('ProviderList', () => { }) it('keeps MCP tab visible without mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = ['tool.manage'] + mockConsoleState.workspacePermissionKeys = ['tool.manage'] renderProviderList() @@ -469,7 +435,7 @@ describe('ProviderList', () => { ['api', 'card-my-api'], ['workflow', 'card-wf-tool'], ] as const)('renders %s category read-only without tool.manage', (category, cardTestId) => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] renderProviderList({ category }) @@ -648,12 +614,12 @@ describe('ProviderList', () => { expect(screen.getByRole('searchbox')).toBeInTheDocument() }) - it('uses the plugin update settings dialog from the tools toolbar', () => { + it('shows the plugin update settings action in the tools toolbar', () => { renderProviderList(undefined, 'builtin') - expect(screen.getByText('plugin.autoUpdate.autoUpdate')).toBeInTheDocument() - expect(screen.getByText('plugin.autoUpdate.strategy.fixOnly.name')).toBeInTheDocument() - expect(screen.getByTestId('update-setting-dialog')).toBeInTheDocument() + expect( + screen.getByRole('button', { name: 'plugin.autoUpdate.autoUpdate' }), + ).toBeInTheDocument() }) it('hides the tools update settings action when permission management is unavailable', () => { @@ -661,7 +627,9 @@ describe('ProviderList', () => { renderProviderList(undefined, 'builtin') - expect(screen.queryByTestId('update-setting-dialog')).not.toBeInTheDocument() + expect( + screen.queryByRole('button', { name: 'plugin.autoUpdate.autoUpdate' }), + ).not.toBeInTheDocument() }) it.each([['mcp'], ['api'], ['workflow']] as const)( @@ -669,9 +637,8 @@ describe('ProviderList', () => { (category) => { renderProviderList({ category }) - expect(screen.queryByText('plugin.autoUpdate.autoUpdate')).not.toBeInTheDocument() expect( - screen.queryByText('plugin.autoUpdate.strategy.fixOnly.name'), + screen.queryByRole('button', { name: 'plugin.autoUpdate.autoUpdate' }), ).not.toBeInTheDocument() }, ) @@ -919,7 +886,7 @@ describe('ProviderList', () => { }) it('renders MCP list read-only without mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = ['tool.manage'] + mockConsoleState.workspacePermissionKeys = ['tool.manage'] renderProviderList({ category: 'mcp' }) diff --git a/web/app/components/main-nav/__tests__/index.spec.tsx b/web/app/components/main-nav/__tests__/index.spec.tsx index bf6b3cffc45..db516d11bd5 100644 --- a/web/app/components/main-nav/__tests__/index.spec.tsx +++ b/web/app/components/main-nav/__tests__/index.spec.tsx @@ -1,17 +1,12 @@ import type { ReactNode } from 'react' import type { Mock } from 'vitest' -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { ModalContextState } from '@/context/modal-context' import type { ProviderContextState } from '@/context/provider-context' import type { ICurrentWorkspace, IWorkspace } from '@/models/common' import type { InstalledApp } from '@/models/explore' +import type { ConsoleStateFixture } from '@/test/console/state-fixture' import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { Provider as JotaiProvider } from 'jotai' -import { - createTestQueryClient, - renderWithSystemFeatures, -} from '@/__tests__/utils/mock-system-features' import { Plan } from '@/app/components/billing/type' import { DETAIL_SIDEBAR_STORAGE_KEY } from '@/app/components/detail-sidebar/storage' import { LEARN_DIFY_HIDDEN_STORAGE_KEY } from '@/app/components/explore/learn-dify/storage' @@ -23,6 +18,7 @@ import { userProfileQueryOptions } from '@/features/account-profile/client' import { usePathname, useRouter } from '@/next/navigation' import { consoleQuery } from '@/service/client' import { useGetInstalledApps, useUninstallApp, useUpdateAppPinStatus } from '@/service/use-explore' +import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import { MainNav } from '../index' @@ -34,39 +30,29 @@ const { mockIsAgentV2Enabled, mockSwitchWorkspace, mockToastSuccess } = vi.hoist mockToastSuccess: vi.fn(), mockIsAgentV2Enabled: vi.fn(() => true), })) -const mockAppContextState = vi.hoisted(() => ({ - current: undefined as AppContextStateMockState | undefined, +const mockConsoleState = vi.hoisted(() => ({ + current: undefined as ConsoleStateFixture | undefined, })) vi.mock('@/features/agent-v2/feature-flag', () => ({ isAgentV2Enabled: () => mockIsAgentV2Enabled(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current ?? {}) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState.current ?? {}) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current ?? {}) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {}) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState.current ?? {}) }) vi.mock('@/context/provider-context', () => ({ @@ -221,9 +207,9 @@ const mainNavUserProfile = { is_password_set: true, } -const appContextValue: AppContextStateMockState = { +const consoleState: ConsoleStateFixture = { userProfile: mainNavUserProfile, - mutateUserProfile: vi.fn(), + refreshUserProfile: vi.fn(), currentWorkspace: { id: 'workspace-1', name: 'Solar Studio', @@ -240,7 +226,7 @@ const appContextValue: AppContextStateMockState = { isCurrentWorkspaceOwner: true, isCurrentWorkspaceEditor: true, isCurrentWorkspaceDatasetOperator: false, - mutateCurrentWorkspace: vi.fn(), + refreshCurrentWorkspace: vi.fn(), langGeniusVersionInfo: { current_env: 'testing', current_version: '1.0.0', @@ -256,7 +242,7 @@ const appContextValue: AppContextStateMockState = { } type MainNavSystemFeatures = Exclude< - NonNullable[1]>['systemFeatures'], + NonNullable[1]>['systemFeatures'], null | undefined > @@ -269,17 +255,17 @@ const renderMainNav = ( systemFeatures: MainNavSystemFeatures = defaultMainNavSystemFeatures, options: { extra?: ReactNode } = {}, ) => { - const queryClient = createTestQueryClient() - const currentAppContext = mockAppContextState.current ?? appContextValue - mockAppContextState.current = currentAppContext + const queryClient = createConsoleQueryClient() + const currentConsoleState = mockConsoleState.current ?? consoleState + mockConsoleState.current = currentConsoleState queryClient.setQueryData( consoleQuery.workspaces.current.post.queryKey(), - currentAppContext.currentWorkspace as ICurrentWorkspace, + currentConsoleState.currentWorkspace as ICurrentWorkspace, ) queryClient.setQueryData(userProfileQueryOptions().queryKey, { profile: { ...mainNavUserProfile, - ...(currentAppContext.userProfile ?? {}), + ...(currentConsoleState.userProfile ?? {}), }, meta: { currentVersion: null, @@ -295,11 +281,11 @@ const renderMainNav = ( ...systemFeatures.branding, }, } - return renderWithSystemFeatures( - + return renderWithConsoleQuery( + <> {options.extra} - , + , { systemFeatures: resolvedSystemFeatures, queryClient }, ) } @@ -341,7 +327,7 @@ describe('MainNav', () => { forward: vi.fn(), refresh: vi.fn(), }) - mockAppContextState.current = appContextValue + mockConsoleState.current = consoleState ;(useProviderContext as Mock).mockReturnValue({ enableBilling: true, isEducationAccount: false, @@ -465,10 +451,10 @@ describe('MainNav', () => { }) it('renders the desktop environment tag from the old header contract', () => { - mockAppContextState.current = { - ...appContextValue, + mockConsoleState.current = { + ...consoleState, langGeniusVersionInfo: { - ...appContextValue.langGeniusVersionInfo, + ...consoleState.langGeniusVersionInfo, current_env: 'TESTING', }, } @@ -507,10 +493,10 @@ describe('MainNav', () => { }) it('keeps unrestricted main routes visible for dataset operators while hiding roster', () => { - mockAppContextState.current = { - ...appContextValue, + mockConsoleState.current = { + ...consoleState, currentWorkspace: { - ...appContextValue.currentWorkspace, + ...consoleState.currentWorkspace, role: 'dataset_operator', }, isCurrentWorkspaceDatasetOperator: true, @@ -544,10 +530,10 @@ describe('MainNav', () => { }) it('keeps unrestricted main routes visible without route permission keys', () => { - mockAppContextState.current = { - ...appContextValue, + mockConsoleState.current = { + ...consoleState, currentWorkspace: { - ...appContextValue.currentWorkspace, + ...consoleState.currentWorkspace, role: 'normal', }, isCurrentWorkspaceDatasetOperator: false, @@ -685,11 +671,7 @@ describe('MainNav', () => { expect(homeLink).toHaveAttribute('aria-current', 'page') mockPathname = '/installed/installed-1' - rerender( - - - , - ) + rerender() expect(screen.getByRole('link', { name: /common.mainNav.home/ })).not.toHaveAttribute( 'aria-current', @@ -861,10 +843,10 @@ describe('MainNav', () => { }) it('limits invite members by member management permission', async () => { - mockAppContextState.current = { - ...appContextValue, + mockConsoleState.current = { + ...consoleState, currentWorkspace: { - ...appContextValue.currentWorkspace, + ...consoleState.currentWorkspace, role: 'normal', }, isCurrentWorkspaceManager: false, @@ -883,10 +865,10 @@ describe('MainNav', () => { }) it('keeps workspace settings visible and hides invite members without member management permission', () => { - mockAppContextState.current = { - ...appContextValue, + mockConsoleState.current = { + ...consoleState, currentWorkspace: { - ...appContextValue.currentWorkspace, + ...consoleState.currentWorkspace, role: 'dataset_operator', }, isCurrentWorkspaceDatasetOperator: true, diff --git a/web/app/components/main-nav/__tests__/layout.spec.tsx b/web/app/components/main-nav/__tests__/layout.spec.tsx index c1a7cdb4b78..89b9af8b152 100644 --- a/web/app/components/main-nav/__tests__/layout.spec.tsx +++ b/web/app/components/main-nav/__tests__/layout.spec.tsx @@ -1,13 +1,14 @@ import type { ReactNode } from 'react' import type { Mock } from 'vitest' import { useSuspenseQuery } from '@tanstack/react-query' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { useStore as useAppStore } from '@/app/components/app/store' import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag' import { usePathname } from '@/next/navigation' +import { render } from '@/test/console/render' import MainNavLayout from '../layout' -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { isCurrentWorkspaceDatasetOperator: false, isCurrentWorkspaceEditor: true, @@ -31,31 +32,9 @@ vi.mock('@tanstack/react-query', async (importOriginal) => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState.current) }) vi.mock('@/features/agent-v2/feature-flag', () => ({ @@ -84,7 +63,7 @@ describe('MainNavLayout', () => { localStorage.clear() useAppStore.getState().setAppDetail() ;(usePathname as Mock).mockReturnValue('/apps') - mockAppContextState.current = { + mockConsoleState.current = { isCurrentWorkspaceDatasetOperator: false, isCurrentWorkspaceEditor: true, } @@ -221,7 +200,7 @@ describe('MainNavLayout', () => { { label: 'agent detail route for dataset operators', pathname: '/agents/agent-1/configure', - appContext: { + consoleState: { isCurrentWorkspaceDatasetOperator: true, isCurrentWorkspaceEditor: true, }, @@ -232,7 +211,7 @@ describe('MainNavLayout', () => { { label: 'deployment detail route for non-editor workspaces', pathname: '/deployments/app-instance-1/overview', - appContext: { + consoleState: { isCurrentWorkspaceDatasetOperator: false, isCurrentWorkspaceEditor: false, }, @@ -243,7 +222,7 @@ describe('MainNavLayout', () => { { label: 'deployment detail route when deployment is disabled', pathname: '/deployments/app-instance-1/overview', - appContext: { + consoleState: { isCurrentWorkspaceDatasetOperator: false, isCurrentWorkspaceEditor: true, }, @@ -251,9 +230,9 @@ describe('MainNavLayout', () => { enable_app_deploy: false, }, }, - ])('keeps the global main nav on $label', ({ pathname, appContext, systemFeatures }) => { + ])('keeps the global main nav on $label', ({ pathname, consoleState, systemFeatures }) => { ;(usePathname as Mock).mockReturnValue(pathname) - mockAppContextState.current = appContext + mockConsoleState.current = consoleState ;(useSuspenseQuery as Mock).mockReturnValue({ data: systemFeatures, }) diff --git a/web/app/components/main-nav/components/__tests__/support-menu.spec.tsx b/web/app/components/main-nav/components/__tests__/support-menu.spec.tsx index 246cf138372..b841f35ef6d 100644 --- a/web/app/components/main-nav/components/__tests__/support-menu.spec.tsx +++ b/web/app/components/main-nav/components/__tests__/support-menu.spec.tsx @@ -4,12 +4,13 @@ import { DropdownMenuContent, DropdownMenuTrigger, } from '@langgenius/dify-ui/dropdown-menu' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { openZendeskWindow } from '@/app/components/base/zendesk/utils' import { Plan } from '@/app/components/billing/type' import { mailToSupport } from '@/app/components/header/utils/util' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' +import { render } from '@/test/console/render' import SupportMenu from '../support-menu' const { mockConfig, mockOpenZendeskWindow, mockMailToSupport, mockSetShowPricingModal } = @@ -23,7 +24,7 @@ const { mockConfig, mockOpenZendeskWindow, mockMailToSupport, mockSetShowPricing mockMailToSupport: vi.fn(), mockSetShowPricingModal: vi.fn(), })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { langGeniusVersionInfo: { current_version: '1.0.0' }, userProfile: { email: 'user@example.com' }, @@ -54,31 +55,13 @@ vi.mock('@/config', async (importOriginal) => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState.current) }) vi.mock('@/context/modal-context', () => ({ @@ -95,7 +78,7 @@ describe('SupportMenu', () => { mockConfig.isCloudEdition = true mockConfig.supportEmailAddress = '' mockConfig.zendeskWidgetKey = 'zendesk-key' - mockAppContextState.current = { + mockConsoleState.current = { langGeniusVersionInfo: { current_version: '1.0.0' }, userProfile: { email: 'user@example.com' }, } diff --git a/web/app/components/main-nav/components/__tests__/workspace-card.spec.tsx b/web/app/components/main-nav/components/__tests__/workspace-card.spec.tsx index c4325b316c0..0bcee2f37bc 100644 --- a/web/app/components/main-nav/components/__tests__/workspace-card.spec.tsx +++ b/web/app/components/main-nav/components/__tests__/workspace-card.spec.tsx @@ -2,16 +2,13 @@ import type { ModalContextState } from '@/context/modal-context' import type { ProviderContextState } from '@/context/provider-context' import type { ICurrentWorkspace, IWorkspace } from '@/models/common' import { fireEvent, screen, waitFor, within } from '@testing-library/react' -import { - createTestQueryClient, - renderWithSystemFeatures, -} from '@/__tests__/utils/mock-system-features' import { Plan } from '@/app/components/billing/type' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' import { LicenseStatus } from '@/features/system-features/constants' import { consoleQuery } from '@/service/client' +import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data' import { WorkspaceCard } from '../workspace-card' const { @@ -25,7 +22,7 @@ const { mockCurrentWorkspaceQueryKey: ['console', 'workspaces', 'current', 'post'] as const, mockWorkspacesQueryKey: ['console', 'workspaces', 'get'] as const, })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ current: { workspacePermissionKeys: [] as string[], }, @@ -45,31 +42,13 @@ vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState.current) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState.current) }) vi.mock('@/context/modal-context', () => ({ @@ -146,26 +125,27 @@ const mockCurrentWorkspaceQuery = ( mockCurrentWorkspace = isPending ? undefined : data } -type RenderWorkspaceCardOptions = Parameters[1] & { +type RenderWorkspaceCardOptions = Parameters[1] & { seedWorkspaces?: boolean } const renderWorkspaceCard = (options?: RenderWorkspaceCardOptions) => { const { seedWorkspaces = true, ...renderOptions } = options ?? {} - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() if (mockCurrentWorkspace) queryClient.setQueryData(consoleQuery.workspaces.current.post.queryKey(), mockCurrentWorkspace) if (seedWorkspaces) queryClient.setQueryData(consoleQuery.workspaces.get.queryKey(), { workspaces: mockWorkspaces }) - return renderWithSystemFeatures(, { + return renderWithConsoleQuery(, { ...renderOptions, queryClient, + currentWorkspace: mockCurrentWorkspace ? undefined : null, }) } const mockWorkspacePermissionKeys = (workspacePermissionKeys: string[]) => { - mockAppContextState.current = { + mockConsoleState.current = { workspacePermissionKeys, } } diff --git a/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts b/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts index 3fb39c7492a..41e338f37ce 100644 --- a/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts +++ b/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts @@ -1,102 +1,117 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { checkForUpdates, fetchReleases, handleUpload } from '../hooks' -const mockNotify = vi.fn() +const { mockToastError, mockUploadGitHub } = vi.hoisted(() => ({ + mockToastError: vi.fn(), + mockUploadGitHub: vi.fn(), +})) + vi.mock('@langgenius/dify-ui/toast', () => ({ - toast: Object.assign((...args: unknown[]) => mockNotify(...args), { - success: (...args: unknown[]) => mockNotify(...args), - error: (...args: unknown[]) => mockNotify(...args), - warning: (...args: unknown[]) => mockNotify(...args), - info: (...args: unknown[]) => mockNotify(...args), - dismiss: vi.fn(), - update: vi.fn(), - promise: vi.fn(), - }), + toast: { + error: mockToastError, + }, })) -const mockUploadGitHub = vi.fn() vi.mock('@/service/plugins', () => ({ - uploadGitHub: (...args: unknown[]) => mockUploadGitHub(...args), + uploadGitHub: mockUploadGitHub, })) -const mockFetch = vi.fn() -globalThis.fetch = mockFetch +const mockFetch = vi.fn() describe('install-plugin/hooks', () => { beforeEach(() => { - vi.clearAllMocks() + mockFetch.mockReset() + mockToastError.mockReset() + mockUploadGitHub.mockReset() + vi.stubGlobal('fetch', mockFetch) }) - describe('useGitHubReleases', () => { - describe('fetchReleases', () => { - it('fetches releases from GitHub API and formats them', async () => { - mockFetch.mockResolvedValue({ - ok: true, - json: () => - Promise.resolve({ - releases: [ - { - tag: 'v1.0.0', - assets: [{ downloadUrl: 'https://example.com/plugin.zip' }], - }, - ], - }), - }) + afterEach(() => { + vi.unstubAllGlobals() + }) - const releases = await fetchReleases('owner', 'repo') + describe('fetchReleases', () => { + it('requests the repository releases and formats the response', async () => { + mockFetch.mockResolvedValue( + new Response( + JSON.stringify({ + releases: [ + { + tag: 'v1.0.0', + assets: [{ downloadUrl: 'https://example.com/plugin.zip' }], + }, + ], + }), + { status: 200 }, + ), + ) - expect(releases).toHaveLength(1) - expect(releases[0].tag_name).toBe('v1.0.0') - expect(releases[0].assets[0].name).toBe('plugin.zip') - expect(releases[0]).not.toHaveProperty('body') - }) + const releases = await fetchReleases('owner', 'repo') - it('returns empty array and shows toast on fetch error', async () => { - mockFetch.mockResolvedValue({ - ok: false, - }) - - const releases = await fetchReleases('owner', 'repo') - - expect(releases).toEqual([]) - expect(mockNotify).toHaveBeenCalledWith('Failed to fetch repository releases') - }) + expect(mockFetch).toHaveBeenCalledOnce() + expect(mockFetch).toHaveBeenCalledWith('https://ungh.cc/repos/owner/repo/releases') + expect(releases).toEqual([ + { + tag_name: 'v1.0.0', + assets: [ + { + browser_download_url: 'https://example.com/plugin.zip', + name: 'plugin.zip', + }, + ], + }, + ]) }) - describe('checkForUpdates', () => { - it('detects newer version available', () => { - const releases = [ - { tag_name: 'v1.0.0', assets: [] }, - { tag_name: 'v2.0.0', assets: [] }, - ] - const { needUpdate, toastProps } = checkForUpdates(releases, 'v1.0.0') - expect(needUpdate).toBe(true) - expect(toastProps.message).toContain('v2.0.0') - }) + it('returns no releases and reports a failed response', async () => { + mockFetch.mockResolvedValue(new Response(null, { status: 500 })) - it('returns no update when current is latest', () => { - const releases = [{ tag_name: 'v1.0.0', assets: [] }] - const { needUpdate, toastProps } = checkForUpdates(releases, 'v1.0.0') - expect(needUpdate).toBe(false) - expect(toastProps.type).toBe('info') - }) + const releases = await fetchReleases('owner', 'repo') - it('returns error for empty releases', () => { - const { needUpdate, toastProps } = checkForUpdates([], 'v1.0.0') - expect(needUpdate).toBe(false) - expect(toastProps.type).toBe('error') - expect(toastProps.message).toContain('empty') - }) + expect(mockFetch).toHaveBeenCalledOnce() + expect(mockFetch).toHaveBeenCalledWith('https://ungh.cc/repos/owner/repo/releases') + expect(releases).toEqual([]) + expect(mockToastError).toHaveBeenCalledOnce() + expect(mockToastError).toHaveBeenCalledWith('Failed to fetch repository releases') }) }) - describe('useGitHubUpload', () => { - it('uploads successfully and calls onSuccess', async () => { - const mockManifest = { name: 'test-plugin' } - mockUploadGitHub.mockResolvedValue({ - manifest: mockManifest, + describe('checkForUpdates', () => { + it('detects newer version available', () => { + const releases = [ + { tag_name: 'v1.0.0', assets: [] }, + { tag_name: 'v2.0.0', assets: [] }, + ] + const { needUpdate, toastProps } = checkForUpdates(releases, 'v1.0.0') + + expect(needUpdate).toBe(true) + expect(toastProps.message).toContain('v2.0.0') + }) + + it('returns no update when current is latest', () => { + const releases = [{ tag_name: 'v1.0.0', assets: [] }] + const { needUpdate, toastProps } = checkForUpdates(releases, 'v1.0.0') + + expect(needUpdate).toBe(false) + expect(toastProps.type).toBe('info') + }) + + it('returns error for empty releases', () => { + const { needUpdate, toastProps } = checkForUpdates([], 'v1.0.0') + + expect(needUpdate).toBe(false) + expect(toastProps.type).toBe('error') + expect(toastProps.message).toContain('empty') + }) + }) + + describe('handleUpload', () => { + it('uploads the selected package and calls onSuccess', async () => { + const expectedPackage = { + manifest: { name: 'test-plugin' }, unique_identifier: 'uid-123', - }) + } + mockUploadGitHub.mockResolvedValue(expectedPackage) const onSuccess = vi.fn() const pkg = await handleUpload( @@ -106,23 +121,23 @@ describe('install-plugin/hooks', () => { onSuccess, ) + expect(mockUploadGitHub).toHaveBeenCalledOnce() expect(mockUploadGitHub).toHaveBeenCalledWith( 'https://github.com/owner/repo', 'v1.0.0', 'plugin.difypkg', ) - expect(onSuccess).toHaveBeenCalledWith({ - manifest: mockManifest, - unique_identifier: 'uid-123', - }) - expect(pkg.unique_identifier).toBe('uid-123') + expect(onSuccess).toHaveBeenCalledOnce() + expect(onSuccess).toHaveBeenCalledWith(expectedPackage) + expect(pkg).toEqual(expectedPackage) }) it('shows toast on upload error', async () => { mockUploadGitHub.mockRejectedValue(new Error('Upload failed')) await expect(handleUpload('url', 'v1', 'pkg')).rejects.toThrow('Upload failed') - expect(mockNotify).toHaveBeenCalledWith('Error uploading package') + expect(mockToastError).toHaveBeenCalledOnce() + expect(mockToastError).toHaveBeenCalledWith('Error uploading package') }) }) }) diff --git a/web/app/components/plugins/install-plugin/base/__tests__/use-get-icon.spec.ts b/web/app/components/plugins/install-plugin/base/__tests__/use-get-icon.spec.ts index 68d9e6248e1..5000c31a17d 100644 --- a/web/app/components/plugins/install-plugin/base/__tests__/use-get-icon.spec.ts +++ b/web/app/components/plugins/install-plugin/base/__tests__/use-get-icon.spec.ts @@ -1,37 +1,17 @@ -import { renderHook } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' +import { renderHook } from '@/test/console/render' import useGetIcon from '../use-get-icon' -const mockCurrentWorkspaceIdAtom = vi.hoisted(() => Symbol('currentWorkspaceIdAtom')) - vi.mock('@/config', () => ({ API_PREFIX: 'https://api.example.com', })) -vi.mock('@/context/account-state', () => ({ - currentWorkspaceIdAtom: mockCurrentWorkspaceIdAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - currentWorkspaceIdAtom: mockCurrentWorkspaceIdAtom, -})) -vi.mock('@/context/permission-state', () => ({ - currentWorkspaceIdAtom: mockCurrentWorkspaceIdAtom, -})) -vi.mock('@/context/version-state', () => ({ - currentWorkspaceIdAtom: mockCurrentWorkspaceIdAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - currentWorkspaceIdAtom: mockCurrentWorkspaceIdAtom, -})) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => { - return { - useAtomValue: (atom: unknown) => { - if (atom === mockCurrentWorkspaceIdAtom) return 'workspace-123' - - throw new Error('Unexpected atom') - }, - } + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-123' }, + })) }) describe('useGetIcon', () => { diff --git a/web/app/components/plugins/install-plugin/hooks/__tests__/use-install-plugin-limit.spec.ts b/web/app/components/plugins/install-plugin/hooks/__tests__/use-install-plugin-limit.spec.ts index ba1a1795dba..32fe33f94f4 100644 --- a/web/app/components/plugins/install-plugin/hooks/__tests__/use-install-plugin-limit.spec.ts +++ b/web/app/components/plugins/install-plugin/hooks/__tests__/use-install-plugin-limit.spec.ts @@ -1,7 +1,7 @@ import type { PluginInstallationScope } from '@dify/contracts/api/console/system-features/types.gen' import { describe, expect, it } from 'vitest' -import { renderHookWithSystemFeatures as renderHook } from '@/__tests__/utils/mock-system-features' import { InstallationScope } from '@/features/system-features/constants' +import { renderHookWithConsoleQuery as renderHook } from '@/test/console/query-data' import { pluginInstallLimit } from '../use-install-plugin-limit' const basePlugin = { diff --git a/web/app/components/plugins/install-plugin/hooks/__tests__/use-workspace-plugin-install-permission.spec.ts b/web/app/components/plugins/install-plugin/hooks/__tests__/use-workspace-plugin-install-permission.spec.ts index a9b654163ff..88b84ef1ebc 100644 --- a/web/app/components/plugins/install-plugin/hooks/__tests__/use-workspace-plugin-install-permission.spec.ts +++ b/web/app/components/plugins/install-plugin/hooks/__tests__/use-workspace-plugin-install-permission.spec.ts @@ -1,12 +1,12 @@ -import { renderHook } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { renderHook } from '@/test/console/render' import useWorkspacePluginInstallPermission from '../use-workspace-plugin-install-permission' let mockWorkspacePermissionKeys: string[] = [] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ langGeniusVersionInfo: { current_env: '', current_version: '1.0.0', @@ -19,9 +19,9 @@ vi.mock('@/context/account-state', async (importOriginal) => { workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => ({ langGeniusVersionInfo: { current_env: '', current_version: '1.0.0', @@ -34,57 +34,6 @@ vi.mock('@/context/workspace-state', async (importOriginal) => { workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - langGeniusVersionInfo: { - current_env: '', - current_version: '1.0.0', - latest_version: '', - release_date: '', - release_notes: '', - version: '', - can_auto_update: false, - }, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - langGeniusVersionInfo: { - current_env: '', - current_version: '1.0.0', - latest_version: '', - release_date: '', - release_notes: '', - version: '', - can_auto_update: false, - }, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - langGeniusVersionInfo: { - current_env: '', - current_version: '1.0.0', - latest_version: '', - release_date: '', - release_notes: '', - version: '', - can_auto_update: false, - }, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) describe('useWorkspacePluginInstallPermission', () => { beforeEach(() => { diff --git a/web/app/components/plugins/install-plugin/install-bundle/__tests__/index.spec.tsx b/web/app/components/plugins/install-plugin/install-bundle/__tests__/index.spec.tsx index a5a625870cb..f53b12ed78e 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/__tests__/index.spec.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/__tests__/index.spec.tsx @@ -9,7 +9,7 @@ import type { } from '../../../types' import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { InstallStep, PluginCategoryEnum } from '../../../types' import InstallBundle, { InstallType } from '../index' import GithubItem from '../item/github-item' diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/__tests__/install-multi.spec.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/__tests__/install-multi.spec.tsx index ac76c07a1f0..3e385d0ea92 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/__tests__/install-multi.spec.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/__tests__/install-multi.spec.tsx @@ -8,7 +8,7 @@ import type { import { act, fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import { PluginCategoryEnum } from '../../../../types' import InstallMulti from '../install-multi' diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/hooks/__tests__/use-install-multi-state.spec.ts b/web/app/components/plugins/install-plugin/install-bundle/steps/hooks/__tests__/use-install-multi-state.spec.ts index 095ce7a0cd2..97364462088 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/hooks/__tests__/use-install-multi-state.spec.ts +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/hooks/__tests__/use-install-multi-state.spec.ts @@ -7,8 +7,8 @@ import type { } from '@/app/components/plugins/types' import { act, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderHookWithSystemFeatures as renderHook } from '@/__tests__/utils/mock-system-features' import { PluginCategoryEnum } from '@/app/components/plugins/types' +import { renderHookWithConsoleQuery as renderHook } from '@/test/console/query-data' import { getPluginKey, useInstallMultiState } from '../use-install-multi-state' let mockMarketplaceData: ReturnType | null = null diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/steps/__tests__/install.spec.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/steps/__tests__/install.spec.tsx index 522411e8624..37739ea19e2 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/steps/__tests__/install.spec.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/steps/__tests__/install.spec.tsx @@ -1,6 +1,7 @@ import type { PluginDeclaration } from '../../../../types' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { PluginCategoryEnum, TaskStatus } from '../../../../types' import Install from '../install' @@ -57,35 +58,17 @@ vi.mock('../../../base/check-task-status', () => ({ }), })) -const mockAppContextState = vi.hoisted(() => ({ - langGeniusVersionInfoAtom: Symbol('langGeniusVersionInfoAtom'), +const mockConsoleState = vi.hoisted(() => ({ langGeniusVersionInfo: { current_version: '1.0.0' as string | undefined }, })) -vi.mock('@/context/account-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) -vi.mock('@/context/permission-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) -vi.mock('@/context/version-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => ({ - useAtomValue: (atom: unknown) => { - if (atom === mockAppContextState.langGeniusVersionInfoAtom) - return mockAppContextState.langGeniusVersionInfo - - throw new Error('Unexpected atom') - }, -})) + return createVersionStateModuleMock(() => ({ + langGeniusVersionInfo: mockConsoleState.langGeniusVersionInfo, + })) +}) vi.mock('../../../../card', () => ({ default: ({ @@ -500,7 +483,7 @@ describe('Install', () => { // ================================ describe('Dify Version Compatibility', () => { it('should not show warning when dify version is compatible', () => { - mockAppContextState.langGeniusVersionInfo.current_version = '1.0.0' + mockConsoleState.langGeniusVersionInfo.current_version = '1.0.0' const payload = createMockManifest({ meta: { version: '1.0.0', minimum_dify_version: '0.8.0' }, }) @@ -511,7 +494,7 @@ describe('Install', () => { }) it('should show warning when dify version is incompatible', () => { - mockAppContextState.langGeniusVersionInfo.current_version = '1.0.0' + mockConsoleState.langGeniusVersionInfo.current_version = '1.0.0' const payload = createMockManifest({ meta: { version: '1.0.0', minimum_dify_version: '2.0.0' }, }) @@ -522,7 +505,7 @@ describe('Install', () => { }) it('should be compatible when minimum_dify_version is undefined', () => { - mockAppContextState.langGeniusVersionInfo.current_version = '1.0.0' + mockConsoleState.langGeniusVersionInfo.current_version = '1.0.0' const payload = createMockManifest({ meta: { version: '1.0.0' } }) render() @@ -531,7 +514,7 @@ describe('Install', () => { }) it('should be compatible when current_version is empty', () => { - mockAppContextState.langGeniusVersionInfo.current_version = '' + mockConsoleState.langGeniusVersionInfo.current_version = '' const payload = createMockManifest({ meta: { version: '1.0.0', minimum_dify_version: '2.0.0' }, }) @@ -543,7 +526,7 @@ describe('Install', () => { }) it('should be compatible when current_version is undefined', () => { - mockAppContextState.langGeniusVersionInfo.current_version = undefined as unknown as string + mockConsoleState.langGeniusVersionInfo.current_version = undefined as unknown as string const payload = createMockManifest({ meta: { version: '1.0.0', minimum_dify_version: '2.0.0' }, }) diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/__tests__/install.spec.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/__tests__/install.spec.tsx index e81d9c252ba..21f35f51e4b 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/__tests__/install.spec.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/__tests__/install.spec.tsx @@ -1,7 +1,8 @@ import type { Plugin, PluginManifestInMarket } from '../../../../types' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { act } from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { PluginCategoryEnum, TaskStatus } from '../../../../types' import Install from '../install' @@ -64,9 +65,8 @@ const mockStopTaskStatus = vi.fn() const mockHandleInstallTaskStart = vi.fn() let mockPluginDeclaration: { manifest: { meta: { minimum_dify_version: string } } } | undefined let mockCanInstall = true -const mockAppContextState = vi.hoisted(() => ({ - langGeniusVersionInfoAtom: Symbol('langGeniusVersionInfoAtom'), - langGeniusVersionInfo: { current_version: '1.0.0' as string | null }, +const mockConsoleState = vi.hoisted(() => ({ + langGeniusVersionInfo: { current_version: '1.0.0' }, })) // Mock useCheckInstalled @@ -78,30 +78,13 @@ vi.mock('@/app/components/plugins/install-plugin/hooks/use-check-installed', () }), })) -vi.mock('@/context/account-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) -vi.mock('@/context/permission-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) -vi.mock('@/context/version-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom, -})) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => ({ - useAtomValue: (atom: unknown) => { - if (atom === mockAppContextState.langGeniusVersionInfoAtom) - return mockAppContextState.langGeniusVersionInfo - - throw new Error('Unexpected atom') - }, -})) + return createVersionStateModuleMock(() => ({ + langGeniusVersionInfo: mockConsoleState.langGeniusVersionInfo, + })) +}) // Mock service hooks vi.mock('@/service/use-plugins', () => ({ @@ -130,7 +113,7 @@ vi.mock('../../../base/check-task-status', () => ({ vi.mock('@/app/components/plugins/install-plugin/hooks/use-plugin-install-permission', () => ({ default: () => ({ canInstallPlugin: true, - currentDifyVersion: mockAppContextState.langGeniusVersionInfo.current_version, + currentDifyVersion: mockConsoleState.langGeniusVersionInfo.current_version, }), })) @@ -205,7 +188,7 @@ describe('Install Component (steps/install.tsx)', () => { mockIsLoading = false mockPluginDeclaration = undefined mockCanInstall = true - mockAppContextState.langGeniusVersionInfo = { current_version: '1.0.0' } + mockConsoleState.langGeniusVersionInfo = { current_version: '1.0.0' } mockInstallPackageFromMarketPlace.mockResolvedValue({ all_installed: false, task_id: 'task-123', @@ -319,7 +302,7 @@ describe('Install Component (steps/install.tsx)', () => { }) it('should not show warning when dify version is compatible', () => { - mockAppContextState.langGeniusVersionInfo = { current_version: '2.0.0' } + mockConsoleState.langGeniusVersionInfo = { current_version: '2.0.0' } mockPluginDeclaration = { manifest: { meta: { minimum_dify_version: '1.0.0' } }, } @@ -329,7 +312,7 @@ describe('Install Component (steps/install.tsx)', () => { }) it('should show warning when dify version is incompatible', () => { - mockAppContextState.langGeniusVersionInfo = { current_version: '1.0.0' } + mockConsoleState.langGeniusVersionInfo = { current_version: '1.0.0' } mockPluginDeclaration = { manifest: { meta: { minimum_dify_version: '2.0.0' } }, } @@ -787,7 +770,7 @@ describe('Install Component (steps/install.tsx)', () => { }) it('should handle null current_version in langGeniusVersionInfo', () => { - mockAppContextState.langGeniusVersionInfo = { current_version: null as unknown as string } + mockConsoleState.langGeniusVersionInfo = { current_version: null as unknown as string } mockPluginDeclaration = { manifest: { meta: { minimum_dify_version: '1.0.0' } }, } diff --git a/web/app/components/plugins/marketplace/__tests__/query.spec.tsx b/web/app/components/plugins/marketplace/__tests__/query.spec.tsx index 7279c96615f..c00d4b49f1f 100644 --- a/web/app/components/plugins/marketplace/__tests__/query.spec.tsx +++ b/web/app/components/plugins/marketplace/__tests__/query.spec.tsx @@ -34,7 +34,7 @@ vi.mock('@/service/client', () => ({ }, })) -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 }, @@ -42,7 +42,7 @@ const createTestQueryClient = () => }) const createWrapper = () => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() const Wrapper = ({ children }: { children: ReactNode }) => ( {children} ) diff --git a/web/app/components/plugins/plugin-auth/__tests__/authorized-in-node.spec.tsx b/web/app/components/plugins/plugin-auth/__tests__/authorized-in-node.spec.tsx index d0ec101c1a5..1a8db7ccd53 100644 --- a/web/app/components/plugins/plugin-auth/__tests__/authorized-in-node.spec.tsx +++ b/web/app/components/plugins/plugin-auth/__tests__/authorized-in-node.spec.tsx @@ -1,8 +1,9 @@ import type { ReactNode } from 'react' import type { Credential, PluginPayload } from '../types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { AuthCategory, CredentialTypeEnum } from '../types' // ==================== Mock Setup ==================== @@ -11,6 +12,13 @@ const mockGetPluginCredentialInfo = vi.fn() const mockIsPluginCredentialInfoLoading = vi.fn() const mockGetPluginOAuthClientSchema = vi.fn() +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/service/use-plugins-auth', () => ({ useGetPluginCredentialInfo: (url: string) => ({ data: url ? mockGetPluginCredentialInfo() : undefined, @@ -50,7 +58,7 @@ vi.mock('@/service/use-triggers', () => ({ // ==================== Test Utilities ==================== -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 }, @@ -58,7 +66,7 @@ const createTestQueryClient = () => }) const createWrapper = () => { - const testQueryClient = createTestQueryClient() + const testQueryClient = createConsoleQueryClient() return ({ children }: { children: ReactNode }) => ( {children} ) @@ -82,6 +90,14 @@ const createCredential = (overrides: Partial = {}): Credential => ({ // ==================== Tests ==================== +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('AuthorizedInNode Component', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/plugins/plugin-auth/__tests__/plugin-auth-in-agent.spec.tsx b/web/app/components/plugins/plugin-auth/__tests__/plugin-auth-in-agent.spec.tsx index 71f58612a10..80f9e243834 100644 --- a/web/app/components/plugins/plugin-auth/__tests__/plugin-auth-in-agent.spec.tsx +++ b/web/app/components/plugins/plugin-auth/__tests__/plugin-auth-in-agent.spec.tsx @@ -1,8 +1,9 @@ import type { ReactNode } from 'react' import type { Credential, PluginPayload } from '../types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { AuthCategory, CredentialTypeEnum } from '../types' // ==================== Mock Setup ==================== @@ -43,52 +44,30 @@ const mockUserProfile = { avatar_url: '', } -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: mockUserProfile, isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ userProfile: mockUserProfile, isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: mockUserProfile, isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), - workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockUserProfile, - isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(), - workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/hooks/use-oauth', () => ({ openOAuthPopup: vi.fn(), @@ -102,7 +81,7 @@ vi.mock('@/service/use-triggers', () => ({ // ==================== Test Utilities ==================== -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 }, @@ -110,7 +89,7 @@ const createTestQueryClient = () => }) const createWrapper = () => { - const testQueryClient = createTestQueryClient() + const testQueryClient = createConsoleQueryClient() return ({ children }: { children: ReactNode }) => ( {children} ) diff --git a/web/app/components/plugins/plugin-auth/__tests__/plugin-auth.spec.tsx b/web/app/components/plugins/plugin-auth/__tests__/plugin-auth.spec.tsx index 1b9396716f0..1423f3896d5 100644 --- a/web/app/components/plugins/plugin-auth/__tests__/plugin-auth.spec.tsx +++ b/web/app/components/plugins/plugin-auth/__tests__/plugin-auth.spec.tsx @@ -1,12 +1,13 @@ -import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { cleanup, fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' +import { render } from '@/test/console/render' import PluginAuth from '../plugin-auth' import { AuthCategory } from '../types' const mockUsePluginAuth = vi.fn() const mockSetShowAccountSettingModal = vi.fn() -const mockAppContext = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'] as string[], })) @@ -23,42 +24,12 @@ vi.mock('../authorized', () => ({ ), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/modal-context', () => ({ useModalContext: () => ({ @@ -74,7 +45,7 @@ const defaultPayload = { describe('PluginAuth', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContext.workspacePermissionKeys = [ + mockConsoleState.workspacePermissionKeys = [ 'credential.use', 'credential.create', 'credential.manage', @@ -149,7 +120,7 @@ describe('PluginAuth', () => { }) it('renders permission hint and disables authorization configuration when credential.create is missing', () => { - mockAppContext.workspacePermissionKeys = ['credential.use'] + mockConsoleState.workspacePermissionKeys = ['credential.use'] mockUsePluginAuth.mockReturnValue({ isAuthorized: false, canOAuth: false, @@ -170,7 +141,7 @@ describe('PluginAuth', () => { }) it('opens members settings when permission hint action is clicked', () => { - mockAppContext.workspacePermissionKeys = ['credential.use'] + mockConsoleState.workspacePermissionKeys = ['credential.use'] mockUsePluginAuth.mockReturnValue({ isAuthorized: false, canOAuth: false, @@ -189,7 +160,7 @@ describe('PluginAuth', () => { }) it('does not render permission hint for datasource authorization', () => { - mockAppContext.workspacePermissionKeys = ['credential.use'] + mockConsoleState.workspacePermissionKeys = ['credential.use'] mockUsePluginAuth.mockReturnValue({ isAuthorized: false, canOAuth: false, @@ -205,7 +176,7 @@ describe('PluginAuth', () => { }) it('does not render permission hint when custom credentials are unavailable', () => { - mockAppContext.workspacePermissionKeys = ['credential.use'] + mockConsoleState.workspacePermissionKeys = ['credential.use'] mockUsePluginAuth.mockReturnValue({ isAuthorized: false, canOAuth: false, diff --git a/web/app/components/plugins/plugin-auth/authorize/__tests__/api-key-modal.spec.tsx b/web/app/components/plugins/plugin-auth/authorize/__tests__/api-key-modal.spec.tsx index 0da6b6b5402..c57970689a7 100644 --- a/web/app/components/plugins/plugin-auth/authorize/__tests__/api-key-modal.spec.tsx +++ b/web/app/components/plugins/plugin-auth/authorize/__tests__/api-key-modal.spec.tsx @@ -2,30 +2,40 @@ import type { ApiKeyModalProps } from '../api-key-modal' import type { FormSchema } from '@/app/components/base/form/types' import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { AuthCategory } from '../../types' -const mockNotify = vi.fn() -const mockToast = { - success: (message: string, options?: Record) => - mockNotify({ type: 'success', message, ...options }), - error: (message: string, options?: Record) => - mockNotify({ type: 'error', message, ...options }), - warning: (message: string, options?: Record) => - mockNotify({ type: 'warning', message, ...options }), - info: (message: string, options?: Record) => - mockNotify({ type: 'info', message, ...options }), - dismiss: vi.fn(), - update: vi.fn(), - promise: vi.fn(), -} +const { mockToast } = vi.hoisted(() => { + const mockNotify = vi.fn() + return { + mockToast: { + success: (message: string, options?: Record) => + mockNotify({ type: 'success', message, ...options }), + error: (message: string, options?: Record) => + mockNotify({ type: 'error', message, ...options }), + warning: (message: string, options?: Record) => + mockNotify({ type: 'warning', message, ...options }), + info: (message: string, options?: Record) => + mockNotify({ type: 'info', message, ...options }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + } +}) vi.mock('@langgenius/dify-ui/toast', () => ({ toast: mockToast, })) + +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: {} })) +}) const mockAddPluginCredential = vi.fn().mockResolvedValue({}) const mockUpdatePluginCredential = vi.fn().mockResolvedValue({}) const defaultCredentialSchemas = [ @@ -87,14 +97,10 @@ vi.mock('@/app/components/base/form/types', () => ({ FormTypeEnum: { textInput: 'text-input' }, })) -// PermissionSelector (rendered for create mode) calls useMembers via TanStack Query. -// Stub it so tests don't need a QueryClientProvider wrapper. vi.mock('@/service/use-common', () => ({ useMembers: () => ({ data: { accounts: [] } }), })) -// PermissionSelector also reads userProfile from app-context. - const basePayload = { category: AuthCategory.tool, provider: 'test-provider', diff --git a/web/app/components/plugins/plugin-auth/authorize/__tests__/authorize-components.spec.tsx b/web/app/components/plugins/plugin-auth/authorize/__tests__/authorize-components.spec.tsx index 7ba2d3cf047..02e52ab0aaf 100644 --- a/web/app/components/plugins/plugin-auth/authorize/__tests__/authorize-components.spec.tsx +++ b/web/app/components/plugins/plugin-auth/authorize/__tests__/authorize-components.spec.tsx @@ -1,28 +1,22 @@ -import type { ReactNode } from 'react' import type { PluginPayload } from '../../types' import type { FormSchema } from '@/app/components/base/form/types' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { QueryClient } from '@tanstack/react-query' +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { createQueryClientWrapper } from '@/test/console/query-client' import { AuthCategory } from '../../types' -// Create a wrapper with QueryClientProvider -const createTestQueryClient = () => - new QueryClient({ - defaultOptions: { - queries: { - retry: false, - gcTime: 0, +const createWrapper = () => + createQueryClientWrapper( + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: 0, + }, }, - }, - }) - -const createWrapper = () => { - const testQueryClient = createTestQueryClient() - return ({ children }: { children: ReactNode }) => ( - {children} + }), ) -} // Mock API hooks - these make network requests so must be mocked const mockGetPluginOAuthUrl = vi.fn() @@ -80,6 +74,10 @@ vi.mock('@/service/use-triggers', () => ({ useInvalidTriggerDynamicOptions: () => vi.fn(), })) +vi.mock('@/service/use-common', () => ({ + useMembers: () => ({ data: { accounts: [] } }), +})) + // Mock AuthForm to control form validation in tests const mockGetFormValues = vi.fn() vi.mock('@/app/components/base/form/form-scenarios/auth', () => ({ @@ -90,24 +88,33 @@ vi.mock('@/app/components/base/form/form-scenarios/auth', () => ({ }), })) -const mockNotify = vi.fn() -const mockToast = { - success: (message: string, options?: Record) => - mockNotify({ type: 'success', message, ...options }), - error: (message: string, options?: Record) => - mockNotify({ type: 'error', message, ...options }), - warning: (message: string, options?: Record) => - mockNotify({ type: 'warning', message, ...options }), - info: (message: string, options?: Record) => - mockNotify({ type: 'info', message, ...options }), - dismiss: vi.fn(), - update: vi.fn(), - promise: vi.fn(), -} +const { mockToast } = vi.hoisted(() => { + const mockNotify = vi.fn() + return { + mockToast: { + success: (message: string, options?: Record) => + mockNotify({ type: 'success', message, ...options }), + error: (message: string, options?: Record) => + mockNotify({ type: 'error', message, ...options }), + warning: (message: string, options?: Record) => + mockNotify({ type: 'warning', message, ...options }), + info: (message: string, options?: Record) => + mockNotify({ type: 'info', message, ...options }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + } +}) vi.mock('@langgenius/dify-ui/toast', () => ({ toast: mockToast, })) + +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: {} })) +}) // Factory function for creating test PluginPayload const createPluginPayload = (overrides: Partial = {}): PluginPayload => ({ category: AuthCategory.tool, @@ -1075,7 +1082,9 @@ describe('ApiKeyModal', () => { expect(apiCallCount).toBe(1) // Clean up by resolving the promise - resolveFirstCall() + await act(async () => { + resolveFirstCall() + }) }) it('should call onRemove when extra button is clicked in edit mode', async () => { @@ -1573,7 +1582,9 @@ describe('OAuthClientSettings', () => { expect(apiCallCount).toBe(1) // Clean up - resolveFirstCall() + await act(async () => { + resolveFirstCall() + }) }) it('should return early from handleRemove if doingActionRef is true', async () => { @@ -1633,7 +1644,9 @@ describe('OAuthClientSettings', () => { expect(deleteCallCount).toBe(1) // Clean up - resolveFirstCall() + await act(async () => { + resolveFirstCall() + }) }) }) diff --git a/web/app/components/plugins/plugin-auth/authorize/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-auth/authorize/__tests__/index.spec.tsx index e58ae4c9dd4..3a097712301 100644 --- a/web/app/components/plugins/plugin-auth/authorize/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-auth/authorize/__tests__/index.spec.tsx @@ -1,13 +1,14 @@ import type { ReactNode } from 'react' import type { PluginPayload } from '../../types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { AuthCategory } from '../../types' import Authorize from '../index' // Create a wrapper with QueryClientProvider for real component testing -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { @@ -18,7 +19,7 @@ const createTestQueryClient = () => }) const createWrapper = () => { - const testQueryClient = createTestQueryClient() + const testQueryClient = createConsoleQueryClient() return ({ children }: { children: ReactNode }) => ( {children} ) @@ -26,7 +27,7 @@ const createWrapper = () => { // Mock API hooks - only mock network-related hooks const mockGetPluginOAuthClientSchema = vi.fn() -const mockAppContext = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'] as string[], })) @@ -62,42 +63,12 @@ vi.mock('@/hooks/use-oauth', () => ({ openOAuthPopup: vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) // Mock service/use-triggers - API service vi.mock('@/service/use-triggers', () => ({ @@ -122,7 +93,7 @@ const createPluginPayload = (overrides: Partial = {}): PluginPayl describe('Authorize', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContext.workspacePermissionKeys = [ + mockConsoleState.workspacePermissionKeys = [ 'credential.use', 'credential.create', 'credential.manage', @@ -261,7 +232,7 @@ describe('Authorize', () => { describe('credential.create permission', () => { it('should disable OAuth button when credential.create is missing', () => { const pluginPayload = createPluginPayload() - mockAppContext.workspacePermissionKeys = ['credential.use'] + mockConsoleState.workspacePermissionKeys = ['credential.use'] render(, { wrapper: createWrapper(), @@ -272,7 +243,7 @@ describe('Authorize', () => { it('should disable API Key button when credential.create is missing', () => { const pluginPayload = createPluginPayload() - mockAppContext.workspacePermissionKeys = ['credential.use'] + mockConsoleState.workspacePermissionKeys = ['credential.use'] render(, { wrapper: createWrapper(), @@ -466,7 +437,7 @@ describe('Authorize', () => { it('should stay disabled when credential.create is missing and custom credentials are unavailable', () => { const pluginPayload = createPluginPayload() - mockAppContext.workspacePermissionKeys = ['credential.use'] + mockConsoleState.workspacePermissionKeys = ['credential.use'] render( { it('should indicate permission-disabled state for accessibility', () => { const pluginPayload = createPluginPayload() - mockAppContext.workspacePermissionKeys = ['credential.use'] + mockConsoleState.workspacePermissionKeys = ['credential.use'] render(, { wrapper: createWrapper(), diff --git a/web/app/components/plugins/plugin-auth/authorized/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-auth/authorized/__tests__/index.spec.tsx index af417981494..9b3f4b72dc7 100644 --- a/web/app/components/plugins/plugin-auth/authorized/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-auth/authorized/__tests__/index.spec.tsx @@ -1,8 +1,9 @@ import type { ReactNode } from 'react' import type { Credential, PluginPayload } from '../../types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { AuthCategory, CredentialTypeEnum } from '../../types' import Authorized from '../index' @@ -83,52 +84,25 @@ vi.mock('@/hooks/use-oauth', () => ({ vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) -const mockAppContext = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'test-user', name: 'Test User', email: 'test@example.com', avatar_url: '' }, workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'] as string[], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockAppContext.userProfile, - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ + userProfile: mockConsoleState.userProfile, + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockAppContext.userProfile, - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ + userProfile: mockConsoleState.userProfile, + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockAppContext.userProfile, - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockAppContext.userProfile, - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: mockAppContext.userProfile, - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) // Mock service/use-triggers vi.mock('@/service/use-triggers', () => ({ @@ -143,9 +117,17 @@ vi.mock('@/service/use-triggers', () => ({ useInvalidTriggerDynamicOptions: () => vi.fn(), })) +vi.mock('@/service/use-common', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useMembers: () => ({ data: { accounts: [] } }), + } +}) + // ==================== Test Utilities ==================== -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { @@ -156,7 +138,7 @@ const createTestQueryClient = () => }) const createWrapper = () => { - const testQueryClient = createTestQueryClient() + const testQueryClient = createConsoleQueryClient() return ({ children }: { children: ReactNode }) => ( {children} ) @@ -183,7 +165,7 @@ const createCredential = (overrides: Partial = {}): Credential => ({ describe('Authorized Component', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContext.workspacePermissionKeys = [ + mockConsoleState.workspacePermissionKeys = [ 'credential.use', 'credential.create', 'credential.manage', @@ -1427,7 +1409,7 @@ describe('Authorized Component', () => { it('should allow credential.use to set default when credential.manage is missing', () => { const pluginPayload = createPluginPayload() const credentials = [createCredential({ is_default: false })] - mockAppContext.workspacePermissionKeys = ['credential.use'] + mockConsoleState.workspacePermissionKeys = ['credential.use'] render(, { wrapper: createWrapper(), @@ -1441,7 +1423,7 @@ describe('Authorized Component', () => { it('should disable set default when credential.use and credential.manage are missing', () => { const pluginPayload = createPluginPayload() const credentials = [createCredential({ is_default: false })] - mockAppContext.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] render(, { wrapper: createWrapper(), diff --git a/web/app/components/plugins/plugin-auth/authorized/__tests__/item.spec.tsx b/web/app/components/plugins/plugin-auth/authorized/__tests__/item.spec.tsx index dbdd492172c..26cf90178a0 100644 --- a/web/app/components/plugins/plugin-auth/authorized/__tests__/item.spec.tsx +++ b/web/app/components/plugins/plugin-auth/authorized/__tests__/item.spec.tsx @@ -1,50 +1,24 @@ import type { Credential } from '../../types' -import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { cleanup, fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { CredentialTypeEnum } from '../../types' import Item from '../item' -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'test-user' }, workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'test-user' }, workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'test-user' }, - workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'test-user' }, - workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'test-user' }, - workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'], - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) // ==================== Test Utilities ==================== diff --git a/web/app/components/plugins/plugin-detail-panel/__tests__/operation-dropdown.spec.tsx b/web/app/components/plugins/plugin-detail-panel/__tests__/operation-dropdown.spec.tsx index 6d10f6c335f..69f61f0578a 100644 --- a/web/app/components/plugins/plugin-detail-panel/__tests__/operation-dropdown.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/__tests__/operation-dropdown.spec.tsx @@ -1,12 +1,12 @@ import type { ReactElement } from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { PluginSource } from '../../types' import { OperationDropdown } from '../operation-dropdown' const render = (ui: ReactElement, enableMarketplace = true) => - renderWithSystemFeatures(ui, { systemFeatures: { enable_marketplace: enableMarketplace } }) + renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: enableMarketplace } }) const openDropdown = () => { fireEvent.click(screen.getByRole('button', { name: 'plugin.detailPanel.operation.moreActions' })) diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header/__tests__/index.spec.tsx index 18d13f40c46..1a4cb2fced0 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/__tests__/index.spec.tsx @@ -2,9 +2,9 @@ import type { ReactElement } from 'react' import type { PluginDetail } from '@/app/components/plugins/types' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { PluginCategoryEnum, PluginSource } from '@/app/components/plugins/types' -import { createAccountProfileQueryClient } from '@/test/account-profile-query' +import { createAccountProfileQueryClient } from '@/test/console/account-profile' +import { renderWithConsoleQuery } from '@/test/console/query-data' import DetailHeader from '../index' const mockSetTargetVersion = vi.fn() @@ -15,7 +15,7 @@ const mockHandleDelete = vi.fn() const render = (ui: ReactElement) => { const queryClient = createAccountProfileQueryClient({ timezone: 'UTC' }) - return renderWithSystemFeatures(ui, { + return renderWithConsoleQuery(ui, { queryClient, systemFeatures: { enable_marketplace: true }, }) diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-detail-header-state.spec.ts b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-detail-header-state.spec.ts index 04eb78ad983..8020c3ae5e8 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-detail-header-state.spec.ts +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-detail-header-state.spec.ts @@ -1,14 +1,14 @@ import type { PluginDetail } from '../../../../types' import { act } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderHookWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderHookWithConsoleQuery } from '@/test/console/query-data' import { PluginSource } from '../../../../types' import { useDetailHeaderState } from '../use-detail-header-state' let mockEnableMarketplace = true const renderHook = (callback: () => Result) => - renderHookWithSystemFeatures(callback, { + renderHookWithConsoleQuery(callback, { systemFeatures: { enable_marketplace: mockEnableMarketplace }, }) diff --git a/web/app/components/plugins/plugin-item/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-item/__tests__/index.spec.tsx index 11ab5feca41..2320cdce7b6 100644 --- a/web/app/components/plugins/plugin-item/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-item/__tests__/index.spec.tsx @@ -2,14 +2,14 @@ import type { ReactElement } from 'react' import type { PluginDeclaration, PluginDetail } from '../../types' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { PluginCategoryEnum, PluginSource } from '../../types' import PluginItem from '../index' const mockEnableMarketplace = vi.fn(() => true) const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: mockEnableMarketplace() }, }) @@ -74,42 +74,12 @@ const createLangGeniusVersionInfo = (currentVersion: string) => ({ can_auto_update: false, }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => ({ langGeniusVersionInfo: mockLangGeniusVersionInfo(), })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - langGeniusVersionInfo: mockLangGeniusVersionInfo(), - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - langGeniusVersionInfo: mockLangGeniusVersionInfo(), - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - langGeniusVersionInfo: mockLangGeniusVersionInfo(), - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - langGeniusVersionInfo: mockLangGeniusVersionInfo(), - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('../action', () => ({ default: ({ onDelete, pluginName }: { onDelete: () => void; pluginName: string }) => ( diff --git a/web/app/components/plugins/plugin-mutation-model/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-mutation-model/__tests__/index.spec.tsx index a87b82c295b..a9881b64ba2 100644 --- a/web/app/components/plugins/plugin-mutation-model/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-mutation-model/__tests__/index.spec.tsx @@ -1,11 +1,19 @@ import type { Plugin } from '../../types' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { expectLoadingButton } from '@/test/button' +import { render } from '@/test/console/render' import { PluginCategoryEnum } from '../../types' import PluginMutationModal from '../index' +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/hooks/use-theme', () => ({ default: () => ({ theme: 'light' }), })) diff --git a/web/app/components/plugins/plugin-page/__tests__/context-provider.spec.tsx b/web/app/components/plugins/plugin-page/__tests__/context-provider.spec.tsx index 29c1f2c152a..b2237a63566 100644 --- a/web/app/components/plugins/plugin-page/__tests__/context-provider.spec.tsx +++ b/web/app/components/plugins/plugin-page/__tests__/context-provider.spec.tsx @@ -2,7 +2,7 @@ import type { ReactElement, ReactNode } from 'react' import { fireEvent, render, screen } from '@testing-library/react' import { NuqsTestingAdapter } from 'nuqs/adapters/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { createSystemFeaturesWrapper } from '@/__tests__/utils/mock-system-features' +import { createConsoleQueryWrapper } from '@/test/console/query-data' import { usePluginPageContext } from '../context' import { PluginPageContextProvider } from '../context-provider' @@ -21,13 +21,13 @@ const renderWithProviders = ( ui: ReactElement, options: { enableMarketplace: boolean; searchParams?: string } = { enableMarketplace: true }, ) => { - const { wrapper: SystemFeaturesWrapper } = createSystemFeaturesWrapper({ + const { wrapper: ConsoleQueryWrapper } = createConsoleQueryWrapper({ systemFeatures: { enable_marketplace: options.enableMarketplace }, }) const Wrapper = ({ children }: { children: ReactNode }) => ( - + {children} - + ) return render(ui, { wrapper: Wrapper }) } diff --git a/web/app/components/plugins/plugin-page/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-page/__tests__/index.spec.tsx index 28443012cde..bea42215031 100644 --- a/web/app/components/plugins/plugin-page/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/__tests__/index.spec.tsx @@ -3,11 +3,11 @@ import type { PluginPageProps } from '../index' import { act, fireEvent, screen, waitFor } from '@testing-library/react' import { useQueryState } from 'nuqs' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import useDocumentTitle from '@/hooks/use-document-title' import { usePluginInstallation } from '@/hooks/use-query-params' // Import mocked modules for assertions import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins' +import { renderWithConsoleQuery } from '@/test/console/query-data' import PluginPageWithContext from '../index' let mockEnableMarketplace = true @@ -15,8 +15,8 @@ const { mockRouterReplace } = vi.hoisted(() => ({ mockRouterReplace: vi.fn(), })) -const render = (ui: ReactElement, options: Parameters[1] = {}) => - renderWithSystemFeatures(ui, { +const render = (ui: ReactElement, options: Parameters[1] = {}) => + renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: mockEnableMarketplace }, ...options, }) @@ -46,9 +46,9 @@ vi.mock('@/context/i18n', () => ({ useDocLink: () => (path: string) => `https://docs.example.com${path}`, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { @@ -68,9 +68,9 @@ vi.mock('@/context/account-state', async (importOriginal) => { ], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { @@ -90,9 +90,9 @@ vi.mock('@/context/workspace-state', async (importOriginal) => { ], })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => ({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { @@ -112,56 +112,6 @@ vi.mock('@/context/permission-state', async (importOriginal) => { ], })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: true, - isCurrentWorkspaceOwner: false, - langGeniusVersionInfo: { - current_env: 'CLOUD', - current_version: '1.0.0', - latest_version: '1.0.0', - version: '1.0.0', - release_date: '', - release_notes: '', - can_auto_update: false, - }, - workspacePermissionKeys: [ - 'plugin.install', - 'plugin.delete', - 'plugin.debug', - 'plugin.plugin_preferences', - ], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceManager: true, - isCurrentWorkspaceOwner: false, - langGeniusVersionInfo: { - current_env: 'CLOUD', - current_version: '1.0.0', - latest_version: '1.0.0', - version: '1.0.0', - release_date: '', - release_notes: '', - can_auto_update: false, - }, - workspacePermissionKeys: [ - 'plugin.install', - 'plugin.delete', - 'plugin.debug', - 'plugin.plugin_preferences', - ], - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/use-plugins', () => ({ hasPluginPermission: (permission: string | undefined, isAdmin: boolean) => { diff --git a/web/app/components/plugins/plugin-page/__tests__/install-plugin-dropdown.spec.tsx b/web/app/components/plugins/plugin-page/__tests__/install-plugin-dropdown.spec.tsx index 6f1da74f110..cf0a3300a05 100644 --- a/web/app/components/plugins/plugin-page/__tests__/install-plugin-dropdown.spec.tsx +++ b/web/app/components/plugins/plugin-page/__tests__/install-plugin-dropdown.spec.tsx @@ -1,7 +1,7 @@ import type { ReactElement } from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import InstallPluginDropdown from '../install-plugin-dropdown' const { mockSystemFeatures } = vi.hoisted(() => ({ @@ -22,7 +22,7 @@ vi.mock('@/config', async (importOriginal) => { }) const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { systemFeatures: mockSystemFeatures }) + renderWithConsoleQuery(ui, { systemFeatures: mockSystemFeatures }) vi.mock('@/app/components/base/icons/src/vender/solid/files', () => ({ FileZip: () => file, diff --git a/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts b/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts index 3704e5ce27e..27d876c7846 100644 --- a/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts +++ b/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts @@ -1,10 +1,9 @@ -// Import mocks for assertions -import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { LangGeniusVersionInfo } from '@/context/app-context-types' +// Import mocks for assertions +import type { ConsoleStateFixture as BaseConsoleStateFixture } from '@/test/console/state-fixture' import { toast } from '@langgenius/dify-ui/toast' import { waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderHookWithSystemFeatures as renderHook } from '@/__tests__/utils/mock-system-features' import { useInvalidateReferenceSettings, useMutationPluginPermissionSettings, @@ -12,6 +11,7 @@ import { usePluginAutoUpgradeSettings, usePluginPermissionSettings, } from '@/service/use-plugins' +import { renderHookWithConsoleQuery as renderHook } from '@/test/console/query-data' import { PermissionType, PluginCategoryEnum } from '../../types' import useReferenceSetting, { useCanInstallPluginFromMarketplace } from '../use-reference-setting' @@ -29,14 +29,14 @@ const defaultLangGeniusVersionInfo: LangGeniusVersionInfo = { can_auto_update: false, } -type MockAppContextState = Omit & { +type ConsoleStateFixture = Omit & { langGeniusVersionInfo?: Partial } -let mockAppContextState: AppContextStateMockState = {} +let mockConsoleState: ConsoleStateFixture = {} -const setAppContextState = (state: MockAppContextState) => { - mockAppContextState = { +const setConsoleState = (state: ConsoleStateFixture) => { + mockConsoleState = { ...state, langGeniusVersionInfo: { ...defaultLangGeniusVersionInfo, @@ -45,31 +45,17 @@ const setAppContextState = (state: MockAppContextState) => { } } -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => mockConsoleState) }) vi.mock('@/service/use-plugins', () => ({ @@ -88,7 +74,7 @@ describe('useReferenceSetting Hook', () => { toastSuccessSpy.mockClear() // Default mocks - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -164,7 +150,7 @@ describe('useReferenceSetting Hook', () => { }) it('should allow install and debug when plugin permission keys are present', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -184,7 +170,7 @@ describe('useReferenceSetting Hook', () => { }) it('should allow debug for managers with legacy admin permission when RBAC is disabled', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -205,7 +191,7 @@ describe('useReferenceSetting Hook', () => { }) it('should allow debug for owners with legacy admin permission when RBAC is disabled', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: true, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -226,7 +212,7 @@ describe('useReferenceSetting Hook', () => { }) it('should allow debug for normal users when legacy debug permission is everyone and RBAC is disabled', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -249,7 +235,7 @@ describe('useReferenceSetting Hook', () => { }) it('should use plugin keys even when legacy admin permission is configured and RBAC is enabled', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -272,7 +258,7 @@ describe('useReferenceSetting Hook', () => { }) it('should apply legacy noOne plugin permissions when RBAC is disabled', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -300,7 +286,7 @@ describe('useReferenceSetting Hook', () => { describe('canSetPermissions', () => { it('should be true with plugin preferences permission when RBAC is disabled', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -313,7 +299,7 @@ describe('useReferenceSetting Hook', () => { }) it('should be false when RBAC is enabled even with plugin preferences permission', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: true, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -329,7 +315,7 @@ describe('useReferenceSetting Hook', () => { }) it('should be false without plugin preferences permission', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -407,7 +393,7 @@ describe('useReferenceSetting Hook', () => { }) it('should keep permission key access available when reference setting data is still loading', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -425,7 +411,7 @@ describe('useReferenceSetting Hook', () => { }) it('should keep permission state loading while workspace permission keys are loading', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, isLoadingWorkspacePermissionKeys: true, @@ -440,7 +426,7 @@ describe('useReferenceSetting Hook', () => { }) it('should keep permission state loading while current workspace is loading', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, isLoadingCurrentWorkspace: true, @@ -456,7 +442,7 @@ describe('useReferenceSetting Hook', () => { describe('RBAC permissions', () => { it('should use workspace permission keys when RBAC is enabled', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -489,7 +475,7 @@ describe('useReferenceSetting Hook', () => { }) it('should ignore legacy plugin permission settings when RBAC is enabled', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -516,7 +502,7 @@ describe('useCanInstallPluginFromMarketplace Hook', () => { beforeEach(() => { vi.clearAllMocks() - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -555,7 +541,7 @@ describe('useCanInstallPluginFromMarketplace Hook', () => { }) it('should return false without plugin.install', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -570,7 +556,7 @@ describe('useCanInstallPluginFromMarketplace Hook', () => { }) it('should return false when both marketplace is disabled and plugin.install is missing', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, @@ -612,7 +598,7 @@ describe('useCanInstallPluginFromMarketplace Hook', () => { }) it('should use plugin.install when marketplace and RBAC are enabled', () => { - setAppContextState({ + setConsoleState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, diff --git a/web/app/components/plugins/plugin-page/empty/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-page/empty/__tests__/index.spec.tsx index 85c1369721c..5304c1381c5 100644 --- a/web/app/components/plugins/plugin-page/empty/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/empty/__tests__/index.spec.tsx @@ -3,8 +3,8 @@ import type { ReactElement } from 'react' import type { FilterState } from '../../filter-management' import { act, fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { InstallationScope } from '@/features/system-features/constants' +import { renderWithConsoleQuery } from '@/test/console/query-data' // ==================== Imports (after mocks) ==================== import Empty from '../index' @@ -39,7 +39,7 @@ const { mockSetActiveTab, mockUseInstalledPluginList, mockState } = vi.hoisted(( }) const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { systemFeatures: mockState.systemFeatures }) + renderWithConsoleQuery(ui, { systemFeatures: mockState.systemFeatures }) // Mock plugin page context vi.mock('../../context', () => ({ diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx index a536dc7a4eb..8258c6d2d13 100644 --- a/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx @@ -1,9 +1,10 @@ import type { PluginStatus } from '@/app/components/plugins/types' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { PluginSource, TaskStatus } from '@/app/components/plugins/types' // Import mocked modules import { useMutationClearTaskPlugin, usePluginTaskList } from '@/service/use-plugins' +import { renderWithConsoleQuery as render } from '@/test/console/query-data' import PluginTaskList from '../components/plugin-task-list' import TaskStatusIndicator from '../components/task-status-indicator' import { usePluginTaskStatus } from '../hooks' diff --git a/web/app/components/plugins/reference-setting-modal/__tests__/index.spec.tsx b/web/app/components/plugins/reference-setting-modal/__tests__/index.spec.tsx index 438ed4fa1f0..db94bb50309 100644 --- a/web/app/components/plugins/reference-setting-modal/__tests__/index.spec.tsx +++ b/web/app/components/plugins/reference-setting-modal/__tests__/index.spec.tsx @@ -4,8 +4,8 @@ import type { Permissions, ReferenceSetting } from '@/app/components/plugins/typ import { fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { PermissionType } from '@/app/components/plugins/types' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { AUTO_UPDATE_MODE, AUTO_UPDATE_STRATEGY } from '../auto-update-setting/types' import ReferenceSettingModal from '../index' @@ -15,7 +15,7 @@ const mockSystemFeatures = { } const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { systemFeatures: mockSystemFeatures }) + renderWithConsoleQuery(ui, { systemFeatures: mockSystemFeatures }) let mockDialogOnOpenChange: ((open: boolean) => void) | undefined diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx index f139d18b3f9..0c53e7aa7cb 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx @@ -9,7 +9,7 @@ import utc from 'dayjs/plugin/utc' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' -import { createAccountProfileQueryClient } from '@/test/account-profile-query' +import { createAccountProfileQueryClient } from '@/test/console/account-profile' import { PluginCategoryEnum, PluginSource } from '../../../types' import AutoUpdateSetting from '../index' import NoDataPlaceholder from '../no-data-placeholder' diff --git a/web/app/components/plugins/update-plugin/__tests__/from-market-place.spec.tsx b/web/app/components/plugins/update-plugin/__tests__/from-market-place.spec.tsx index fb7946ad916..73cd65d24e9 100644 --- a/web/app/components/plugins/update-plugin/__tests__/from-market-place.spec.tsx +++ b/web/app/components/plugins/update-plugin/__tests__/from-market-place.spec.tsx @@ -1,7 +1,8 @@ import type { UpdateFromMarketPlacePayload } from '@/app/components/plugins/types' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { PluginCategoryEnum, TaskStatus } from '@/app/components/plugins/types' +import { render } from '@/test/console/render' import UpdateFromMarketplace from '../from-market-place' const { @@ -20,6 +21,13 @@ const { mockToastError: vi.fn(), })) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@langgenius/dify-ui/dialog', () => ({ Dialog: ({ children }: { children: React.ReactNode }) =>
{children}
, DialogContent: ({ children }: { children: React.ReactNode }) =>
{children}
, diff --git a/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx b/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx index b32e0ec95b9..1a9c9db8947 100644 --- a/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx +++ b/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx @@ -6,9 +6,10 @@ import type { } from '../../types' import { toast } from '@langgenius/dify-ui/toast' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { PluginCategoryEnum, PluginSource, TaskStatus } from '../../types' import DowngradeWarningModal from '../downgrade-warning' import FromGitHub from '../from-github' @@ -21,6 +22,13 @@ import PluginVersionPicker from '../plugin-version-picker' // Mock app context for useGetIcon // Mock hooks/use-timestamp +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/hooks/use-timestamp', () => ({ default: () => ({ formatDate: (timestamp: number, _format: string) => { diff --git a/web/app/components/rag-pipeline/components/__tests__/conversion.spec.tsx b/web/app/components/rag-pipeline/components/__tests__/conversion.spec.tsx index d77a051fb69..b9904207354 100644 --- a/web/app/components/rag-pipeline/components/__tests__/conversion.spec.tsx +++ b/web/app/components/rag-pipeline/components/__tests__/conversion.spec.tsx @@ -1,9 +1,17 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import Conversion from '../conversion' const mockConvert = vi.fn() const mockInvalidDatasetDetail = vi.fn() +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/next/navigation', () => ({ useParams: () => ({ datasetId: 'ds-123' }), })) @@ -64,6 +72,14 @@ vi.mock('../screenshot', () => ({ default: () =>
, })) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('Conversion', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/rag-pipeline/components/__tests__/rag-pipeline-main.spec.tsx b/web/app/components/rag-pipeline/components/__tests__/rag-pipeline-main.spec.tsx index 8378fd8797a..e384f581b1f 100644 --- a/web/app/components/rag-pipeline/components/__tests__/rag-pipeline-main.spec.tsx +++ b/web/app/components/rag-pipeline/components/__tests__/rag-pipeline-main.spec.tsx @@ -1,7 +1,8 @@ import type { PropsWithChildren } from 'react' import type { Edge, Node, Viewport } from 'reactflow' -import { cleanup, render, screen } from '@testing-library/react' +import { cleanup, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { DatasetACLPermission } from '@/utils/permission' import RagPipelineMain from '../rag-pipeline-main' @@ -9,6 +10,13 @@ const mockPermissionState = vi.hoisted(() => ({ permissionKeys: ['dataset.acl.edit'] as string[], })) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('../../hooks', () => ({ useAvailableNodesMetaData: () => ({ nodes: [], nodesMap: {} }), useDSL: () => ({ @@ -153,6 +161,14 @@ afterEach(() => { vi.clearAllMocks() }) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('RagPipelineMain', () => { const defaultProps = { nodes: [] as Node[], diff --git a/web/app/components/rag-pipeline/components/panel/input-field/editor/__tests__/index.spec.tsx b/web/app/components/rag-pipeline/components/panel/input-field/editor/__tests__/index.spec.tsx index 42f14cb612f..5fec15e70e0 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/editor/__tests__/index.spec.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/editor/__tests__/index.spec.tsx @@ -105,7 +105,7 @@ const createInputFieldEditorProps = ( ...overrides, }) -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { @@ -116,7 +116,7 @@ const createTestQueryClient = () => }) const TestWrapper = ({ children }: { children: React.ReactNode }) => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() return {children} } diff --git a/web/app/components/rag-pipeline/components/panel/input-field/editor/form/__tests__/index.spec.tsx b/web/app/components/rag-pipeline/components/panel/input-field/editor/form/__tests__/index.spec.tsx index a4587cb9ca5..867283eefe9 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/editor/form/__tests__/index.spec.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/editor/form/__tests__/index.spec.tsx @@ -59,7 +59,7 @@ const createInputFieldFormProps = ( ...overrides, }) -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { @@ -70,7 +70,7 @@ const createTestQueryClient = () => }) const TestWrapper = ({ children }: { children: React.ReactNode }) => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() return {children} } diff --git a/web/app/components/rag-pipeline/components/panel/input-field/preview/__tests__/index.spec.tsx b/web/app/components/rag-pipeline/components/panel/input-field/preview/__tests__/index.spec.tsx index 5e873bc7f55..c27ddd72c7a 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/preview/__tests__/index.spec.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/preview/__tests__/index.spec.tsx @@ -194,7 +194,7 @@ const createDatasourceOption = (overrides?: Partial): DataSour ...overrides, }) -const createTestQueryClient = () => +const createConsoleQueryClient = () => new QueryClient({ defaultOptions: { queries: { @@ -205,7 +205,7 @@ const createTestQueryClient = () => }) const TestWrapper = ({ children }: { children: React.ReactNode }) => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() return {children} } diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx index 2d16fc28ff5..b943abaa3d3 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx @@ -1,8 +1,9 @@ import type { IconInfo } from '@/models/datasets' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import Publisher from '../index' import { Popup } from '../popup' @@ -157,10 +158,9 @@ vi.mock('@/context/dataset-detail', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: mockCurrentUserId, }, @@ -168,10 +168,9 @@ vi.mock('@/context/account-state', async (importOriginal) => { workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: { id: mockCurrentUserId, }, @@ -179,46 +178,6 @@ vi.mock('@/context/workspace-state', async (importOriginal) => { workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: mockCurrentUserId, - }, - isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: mockCurrentUserId, - }, - isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: mockCurrentUserId, - }, - isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) const mockSetShowPricingModal = vi.fn() vi.mock('@/context/modal-context', () => ({ diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/popup.spec.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/popup.spec.tsx index b15b940480f..37ce084d08e 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/popup.spec.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/popup.spec.tsx @@ -1,5 +1,6 @@ -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { Popup } from '../popup' vi.mock('@langgenius/dify-ui/alert-dialog', () => ({ @@ -181,10 +182,9 @@ vi.mock('@/context/dataset-detail', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: mockCurrentUserId, }, @@ -192,10 +192,9 @@ vi.mock('@/context/account-state', async (importOriginal) => { workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: { id: mockCurrentUserId, }, @@ -203,46 +202,6 @@ vi.mock('@/context/workspace-state', async (importOriginal) => { workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: mockCurrentUserId, - }, - isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: mockCurrentUserId, - }, - isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: mockCurrentUserId, - }, - isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys, - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/context/i18n', () => ({ useDocLink: () => () => 'https://docs.dify.ai', diff --git a/web/app/components/share/text-generation/hooks/__tests__/use-text-generation-app-state.spec.ts b/web/app/components/share/text-generation/hooks/__tests__/use-text-generation-app-state.spec.ts index 1657c6bd426..6dc76577d0b 100644 --- a/web/app/components/share/text-generation/hooks/__tests__/use-text-generation-app-state.spec.ts +++ b/web/app/components/share/text-generation/hooks/__tests__/use-text-generation-app-state.spec.ts @@ -1,10 +1,10 @@ import { act, waitFor } from '@testing-library/react' -import { renderHookWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { AppSourceType } from '@/service/share' +import { renderHookWithConsoleQuery } from '@/test/console/query-data' import { useTextGenerationAppState } from '../use-text-generation-app-state' const renderHook = (callback: (props: Props) => Result) => - renderHookWithSystemFeatures(callback, { + renderHookWithConsoleQuery(callback, { systemFeatures: { branding: { enabled: false, workspace_logo: '' } }, }) diff --git a/web/app/components/signin/__tests__/countdown.spec.tsx b/web/app/components/signin/__tests__/countdown.spec.tsx index 5bb72e1b597..c9bb772d5a2 100644 --- a/web/app/components/signin/__tests__/countdown.spec.tsx +++ b/web/app/components/signin/__tests__/countdown.spec.tsx @@ -12,6 +12,7 @@ describe('Countdown', () => { afterEach(() => { vi.useRealTimers() + vi.restoreAllMocks() }) it('shows the stored remaining time', () => { @@ -32,10 +33,11 @@ describe('Countdown', () => { }) it('removes the stored time when the countdown ends', () => { + const removeItemSpy = vi.spyOn(Storage.prototype, 'removeItem') localStorage.setItem(COUNT_DOWN_KEY, '1000') render() act(() => vi.advanceTimersByTime(2000)) - expect(localStorage.removeItem).toHaveBeenCalledWith(COUNT_DOWN_KEY) + expect(removeItemSpy).toHaveBeenCalledWith(COUNT_DOWN_KEY) }) it('resets the countdown and invokes the callback when resent', () => { @@ -45,7 +47,7 @@ describe('Countdown', () => { fireEvent.click(screen.getByRole('button', { name: 'login.checkCode.resend' })) - expect(localStorage.setItem).toHaveBeenCalledWith(COUNT_DOWN_KEY, String(COUNT_DOWN_TIME_MS)) + expect(localStorage.getItem(COUNT_DOWN_KEY)).toBe(String(COUNT_DOWN_TIME_MS)) expect(onResend).toHaveBeenCalledOnce() }) }) diff --git a/web/app/components/snippet-list/__tests__/index.spec.tsx b/web/app/components/snippet-list/__tests__/index.spec.tsx index 7dc703def99..28cf3fc67d7 100644 --- a/web/app/components/snippet-list/__tests__/index.spec.tsx +++ b/web/app/components/snippet-list/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ import { fireEvent, screen } from '@testing-library/react' import * as React from 'react' -import { createSystemFeaturesWrapper } from '@/__tests__/utils/mock-system-features' +import { createConsoleQueryWrapper } from '@/test/console/query-data' import { renderWithNuqs } from '@/test/nuqs-testing' import SnippetList from '..' @@ -73,63 +73,33 @@ const mockIsCurrentWorkspaceEditor = vi.fn(() => true) const mockIsCurrentWorkspaceDatasetOperator = vi.fn(() => false) const mockWorkspacePermissionKeys = vi.fn(() => ['snippets.create_and_modify']) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'creator-1' }, currentWorkspace: { id: 'workspace-1' }, isLoadingCurrentWorkspace: false, workspacePermissionKeys: mockWorkspacePermissionKeys(), })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ userProfile: { id: 'creator-1' }, currentWorkspace: { id: 'workspace-1' }, isLoadingCurrentWorkspace: false, workspacePermissionKeys: mockWorkspacePermissionKeys(), })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'creator-1' }, currentWorkspace: { id: 'workspace-1' }, isLoadingCurrentWorkspace: false, workspacePermissionKeys: mockWorkspacePermissionKeys(), })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'creator-1' }, - currentWorkspace: { id: 'workspace-1' }, - isLoadingCurrentWorkspace: false, - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'creator-1' }, - currentWorkspace: { id: 'workspace-1' }, - isLoadingCurrentWorkspace: false, - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/use-common', () => ({ useMembers: () => ({ @@ -271,14 +241,14 @@ const renderList = ({ }: { brandingEnabled?: boolean } = {}) => { - const { wrapper: SystemFeaturesWrapper } = createSystemFeaturesWrapper({ + const { wrapper: ConsoleQueryWrapper } = createConsoleQueryWrapper({ systemFeatures: { branding: { enabled: brandingEnabled } }, }) return renderWithNuqs( - + - , + , ) } diff --git a/web/app/components/snippet-list/components/__tests__/snippet-card.spec.tsx b/web/app/components/snippet-list/components/__tests__/snippet-card.spec.tsx index 1e61ba2e681..13383d3c5d3 100644 --- a/web/app/components/snippet-list/components/__tests__/snippet-card.spec.tsx +++ b/web/app/components/snippet-list/components/__tests__/snippet-card.spec.tsx @@ -1,5 +1,6 @@ import type { SnippetListItem } from '@/types/snippet' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { render } from '@/test/console/render' import SnippetCard from '../snippet-card' const { @@ -26,53 +27,20 @@ const { mockUpdateMutate: vi.fn(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), workspacePermissionKeys: mockWorkspacePermissionKeys(), })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), workspacePermissionKeys: mockWorkspacePermissionKeys(), })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(), - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/use-common', () => ({ useMembers: () => ({ diff --git a/web/app/components/snippet-list/components/__tests__/snippet-create-button.spec.tsx b/web/app/components/snippet-list/components/__tests__/snippet-create-button.spec.tsx index 5ad43164af0..b7f8eb5f2a9 100644 --- a/web/app/components/snippet-list/components/__tests__/snippet-create-button.spec.tsx +++ b/web/app/components/snippet-list/components/__tests__/snippet-create-button.spec.tsx @@ -1,4 +1,5 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { render } from '@/test/console/render' import SnippetCreateButton from '../snippet-create-button' const { @@ -34,48 +35,12 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys(), })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/use-snippets', () => ({ useCreateSnippetMutation: () => ({ diff --git a/web/app/components/snippets/__tests__/import-snippet-dsl-dialog.spec.tsx b/web/app/components/snippets/__tests__/import-snippet-dsl-dialog.spec.tsx index 23a83185faf..4885e507a5b 100644 --- a/web/app/components/snippets/__tests__/import-snippet-dsl-dialog.spec.tsx +++ b/web/app/components/snippets/__tests__/import-snippet-dsl-dialog.spec.tsx @@ -1,5 +1,6 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import ImportSnippetDSLDialog from '../import-snippet-dsl-dialog' const serviceMocks = vi.hoisted(() => ({ @@ -24,48 +25,12 @@ vi.mock('@/next/navigation', () => ({ useRouter: () => routerMocks, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: contextMocks.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: contextMocks.workspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: contextMocks.workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: contextMocks.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: contextMocks.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@langgenius/dify-ui/toast', () => ({ toast: toastMocks, diff --git a/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx b/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx index 93fef61b3a9..a488e1b86b0 100644 --- a/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx +++ b/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx @@ -25,7 +25,7 @@ const mockHandleStopRun = vi.fn() const mockHandleWorkflowStartRunInWorkflow = vi.fn() const mockHandleCheckBeforePublish = vi.fn() const mockUseAvailableNodesMetaData = vi.hoisted(() => vi.fn()) -const mockAppContext = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['snippets.create_and_modify'] as string[], })) const mockInspectVarsCrud = { @@ -52,48 +52,12 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockAppContext.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) let capturedHooksStore: Record | undefined let capturedWorkflowNodes: WorkflowProps['nodes'] | undefined @@ -297,7 +261,7 @@ const createDraftNode = (id = 'draft-node') => describe('SnippetMain', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContext.workspacePermissionKeys = ['snippets.create_and_modify'] + mockConsoleState.workspacePermissionKeys = ['snippets.create_and_modify'] mockDoSyncWorkflowDraft.mockResolvedValue(undefined) mockSyncInputFieldsDraft.mockResolvedValue(undefined) mockPublishSnippetMutateAsync.mockResolvedValue({ created_at: 1_744_000_000 }) @@ -373,7 +337,7 @@ describe('SnippetMain', () => { }) it('should make the snippet canvas readonly and skip draft sync without create-and-modify permission', async () => { - mockAppContext.workspacePermissionKeys = ['snippets.management'] + mockConsoleState.workspacePermissionKeys = ['snippets.management'] const { store } = renderSnippetMain({ hasPublishedWorkflow: false, diff --git a/web/app/components/snippets/hooks/__tests__/use-create-snippet.spec.tsx b/web/app/components/snippets/hooks/__tests__/use-create-snippet.spec.tsx index 0b1cbfa3095..4492464aaeb 100644 --- a/web/app/components/snippets/hooks/__tests__/use-create-snippet.spec.tsx +++ b/web/app/components/snippets/hooks/__tests__/use-create-snippet.spec.tsx @@ -1,5 +1,6 @@ -import { act, renderHook } from '@testing-library/react' +import { act } from '@testing-library/react' import { PipelineInputVarType } from '@/models/pipeline' +import { renderHook } from '@/test/console/render' import { useCreateSnippet } from '../use-create-snippet' const { @@ -43,48 +44,12 @@ vi.mock('@/service/client', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys(), })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys(), - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@langgenius/dify-ui/toast', () => ({ toast: { diff --git a/web/app/components/tools/mcp/__tests__/create-card.spec.tsx b/web/app/components/tools/mcp/__tests__/create-card.spec.tsx index c5766b93a02..8a51c7b695d 100644 --- a/web/app/components/tools/mcp/__tests__/create-card.spec.tsx +++ b/web/app/components/tools/mcp/__tests__/create-card.spec.tsx @@ -1,8 +1,9 @@ import type { ReactNode } from 'react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import NewMCPCard, { NewMCPButton } from '../create-card' // Track the mock functions @@ -42,35 +43,17 @@ vi.mock('../modal', () => ({ }, })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['mcp.manage'] as string[], - workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'), })) -vi.mock('@/context/account-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/permission-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/version-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => ({ - useAtomValue: (atom: unknown) => { - if (atom === mockAppContextState.workspacePermissionKeysAtom) - return mockAppContextState.workspacePermissionKeys - - throw new Error('Unexpected atom') - }, -})) + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, + })) +}) // Mock the plugins service vi.mock('@/service/use-plugins', () => ({ @@ -108,7 +91,7 @@ describe('NewMCPCard', () => { beforeEach(() => { mockCreateMCP.mockClear() - mockAppContextState.workspacePermissionKeys = ['mcp.manage'] + mockConsoleState.workspacePermissionKeys = ['mcp.manage'] }) describe('Rendering', () => { @@ -168,7 +151,7 @@ describe('NewMCPCard', () => { describe('mcp.manage Permission', () => { it('should not render card when user lacks mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] render(, { wrapper: createWrapper() }) @@ -176,7 +159,7 @@ describe('NewMCPCard', () => { }) it('should not render toolbar button when user lacks mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] render(, { wrapper: createWrapper() }) diff --git a/web/app/components/tools/mcp/__tests__/index.spec.tsx b/web/app/components/tools/mcp/__tests__/index.spec.tsx index 9fb40214e22..abf847d3806 100644 --- a/web/app/components/tools/mcp/__tests__/index.spec.tsx +++ b/web/app/components/tools/mcp/__tests__/index.spec.tsx @@ -1,5 +1,6 @@ -import { act, fireEvent, render, screen } from '@testing-library/react' +import { act, fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import MCPList from '../index' type MockProvider = { @@ -15,9 +16,8 @@ const mockRefetch = vi.fn() const mockUseAllToolProviders = vi.fn() let mockProviders: MockProvider[] = [] let mockIsLoadingToolProviders = false -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['mcp.manage'] as string[], - workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'), })) vi.mock('@/service/use-tools', () => ({ @@ -31,30 +31,13 @@ vi.mock('@/service/use-tools', () => ({ }, })) -vi.mock('@/context/account-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/permission-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/version-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => ({ - useAtomValue: (atom: unknown) => { - if (atom === mockAppContextState.workspacePermissionKeysAtom) - return mockAppContextState.workspacePermissionKeys - - throw new Error('Unexpected atom') - }, -})) + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, + })) +}) vi.mock('@/app/components/tools/provider/tool-card-skeleton', () => ({ default: ({ variant }: { variant?: string }) => ( @@ -157,7 +140,7 @@ describe('MCPList', () => { vi.useFakeTimers() mockProviders = [] mockIsLoadingToolProviders = false - mockAppContextState.workspacePermissionKeys = ['mcp.manage'] + mockConsoleState.workspacePermissionKeys = ['mcp.manage'] mockRefetch.mockResolvedValue(undefined) }) @@ -173,7 +156,7 @@ describe('MCPList', () => { }) it('should render providers read-only when user lacks mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] mockProviders = [{ id: '1', name: 'Provider 1', type: 'mcp' }] render() diff --git a/web/app/components/tools/mcp/__tests__/provider-card.spec.tsx b/web/app/components/tools/mcp/__tests__/provider-card.spec.tsx index 844f7795555..99d5e282245 100644 --- a/web/app/components/tools/mcp/__tests__/provider-card.spec.tsx +++ b/web/app/components/tools/mcp/__tests__/provider-card.spec.tsx @@ -1,9 +1,10 @@ import type { ReactNode } from 'react' import type { ToolWithProvider } from '@/app/components/workflow/types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import MCPCard from '../provider-card' // Mutable mock functions @@ -83,35 +84,17 @@ vi.mock('../detail/operation-dropdown', () => ({ ), })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['mcp.manage'] as string[], - workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'), })) -vi.mock('@/context/account-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/permission-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/version-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => ({ - useAtomValue: (atom: unknown) => { - if (atom === mockAppContextState.workspacePermissionKeysAtom) - return mockAppContextState.workspacePermissionKeys - - throw new Error('Unexpected atom') - }, -})) + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, + })) +}) // Mock the format time hook vi.mock('@/hooks/use-format-time-from-now', () => ({ @@ -182,7 +165,7 @@ describe('MCPCard', () => { mockDeleteMCP.mockClear() mockUpdateMCP.mockResolvedValue({ result: 'success' }) mockDeleteMCP.mockResolvedValue({ result: 'success' }) - mockAppContextState.workspacePermissionKeys = ['mcp.manage'] + mockConsoleState.workspacePermissionKeys = ['mcp.manage'] }) describe('Rendering', () => { @@ -346,7 +329,7 @@ describe('MCPCard', () => { }) it('should not render operation dropdown when user lacks mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] render(, { wrapper: createWrapper() }) diff --git a/web/app/components/tools/mcp/detail/__tests__/content.spec.tsx b/web/app/components/tools/mcp/detail/__tests__/content.spec.tsx index 6c1dacf2143..506c917236a 100644 --- a/web/app/components/tools/mcp/detail/__tests__/content.spec.tsx +++ b/web/app/components/tools/mcp/detail/__tests__/content.spec.tsx @@ -1,9 +1,10 @@ import type { ReactNode } from 'react' import type { ToolWithProvider } from '@/app/components/workflow/types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import MCPDetailContent from '../content' // Mutable mock functions @@ -111,37 +112,19 @@ vi.mock('../tool-item', () => ({ default: ({ tool }: { tool: ToolItemData }) =>
{tool.name}
, })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['mcp.manage'] as string[], - workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'), })) // Mock the app context -vi.mock('@/context/account-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/permission-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/version-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => ({ - useAtomValue: (atom: unknown) => { - if (atom === mockAppContextState.workspacePermissionKeysAtom) - return mockAppContextState.workspacePermissionKeys - - throw new Error('Unexpected atom') - }, -})) + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, + })) +}) // Mock the plugins service vi.mock('@/service/use-plugins', () => ({ @@ -222,7 +205,7 @@ describe('MCPDetailContent', () => { mockIsFetching = false mockIsUpdating = false mockIsAuthorizing = false - mockAppContextState.workspacePermissionKeys = ['mcp.manage'] + mockConsoleState.workspacePermissionKeys = ['mcp.manage'] }) describe('Rendering', () => { @@ -254,7 +237,7 @@ describe('MCPDetailContent', () => { }) it('should render read-only detail when user lacks mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] render(, { wrapper: createWrapper() }) @@ -447,7 +430,7 @@ describe('MCPDetailContent', () => { }) it('should disable authorize action when user lacks mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] const detail = createMockDetail({ is_team_authorization: false }) render(, { wrapper: createWrapper() }) @@ -736,7 +719,7 @@ describe('MCPDetailContent', () => { }) it('should not run OAuth authorization when user lacks mcp.manage', async () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] mockAuthorizeMcp.mockResolvedValue({ authorization_url: 'https://oauth.example.com' }) const detail = createMockDetail({ is_team_authorization: false }) @@ -773,7 +756,7 @@ describe('MCPDetailContent', () => { }) it('should disable authorized button when user lacks mcp.manage', () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] const detail = createMockDetail({ is_team_authorization: true }) render(, { wrapper: createWrapper() }) diff --git a/web/app/components/tools/mcp/hooks/__tests__/use-mcp-service-card.spec.ts b/web/app/components/tools/mcp/hooks/__tests__/use-mcp-service-card.spec.ts index 0df88a7fd00..595ad03971e 100644 --- a/web/app/components/tools/mcp/hooks/__tests__/use-mcp-service-card.spec.ts +++ b/web/app/components/tools/mcp/hooks/__tests__/use-mcp-service-card.spec.ts @@ -1,10 +1,10 @@ -import type { ReactNode } from 'react' import type { AppDetailResponse } from '@/models/app' import type { AppSSO } from '@/types/app' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { act, renderHook } from '@testing-library/react' -import * as React from 'react' +import { act } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { createAccountProfileQueryClient } from '@/test/console/account-profile' +import { createQueryClientWrapper } from '@/test/console/query-client' +import { renderHook } from '@/test/console/render' import { AppModeEnum } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import { useMCPServiceCardState } from '../use-mcp-service-card' @@ -32,6 +32,13 @@ let mockUseMCPServerDetailAppID = '' let mockUseMCPServerDetailEnabled: boolean | undefined // Mock service hooks +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/service/use-tools', () => ({ useUpdateMCPServer: () => ({ mutateAsync: mockUpdateMCPServer, @@ -75,17 +82,17 @@ vi.mock('@/service/apps', () => ({ }), })) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('useMCPServiceCardState', () => { const createWrapper = () => { - const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }) - return ({ children }: { children: ReactNode }) => - React.createElement(QueryClientProvider, { client: queryClient }, children) + return createQueryClientWrapper(createAccountProfileQueryClient()) } const createMockAppInfo = ( diff --git a/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx b/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx index ab5e4091568..98b319224b0 100644 --- a/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx +++ b/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx @@ -1,38 +1,21 @@ import type { CustomCollectionBackend } from '../../types' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { AuthType } from '../../types' import CustomCreateCard, { NewCustomToolButton } from '../custom-create-card' -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: ['tool.manage'] as string[], - workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'), })) -vi.mock('@/context/account-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/permission-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/version-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => ({ - useAtomValue: (atom: unknown) => { - if (atom === mockAppContextState.workspacePermissionKeysAtom) - return mockAppContextState.workspacePermissionKeys - - throw new Error('Unexpected atom') - }, -})) + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, + })) +}) // Mock useLocale and useDocLink vi.mock('@/context/i18n', () => ({ @@ -110,7 +93,7 @@ describe('CustomCreateCard', () => { beforeEach(() => { vi.clearAllMocks() - mockAppContextState.workspacePermissionKeys = ['tool.manage'] + mockConsoleState.workspacePermissionKeys = ['tool.manage'] mockModalVisible = false mockCreateCustomCollection.mockResolvedValue({}) }) @@ -123,7 +106,7 @@ describe('CustomCreateCard', () => { }) it('should not render anything when user does not have tool.manage', () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] const { container } = render() @@ -179,7 +162,7 @@ describe('CustomCreateCard', () => { }) it('should not render toolbar add button when user does not have tool.manage', () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] const { container } = render() diff --git a/web/app/components/tools/provider/__tests__/detail.spec.tsx b/web/app/components/tools/provider/__tests__/detail.spec.tsx index 4bcefd50d62..d0a06a985ac 100644 --- a/web/app/components/tools/provider/__tests__/detail.spec.tsx +++ b/web/app/components/tools/provider/__tests__/detail.spec.tsx @@ -1,6 +1,7 @@ import type { Collection } from '../../types' -import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, cleanup, fireEvent, screen, waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { AuthType, CollectionType } from '../../types' import ProviderDetail from '../detail' @@ -8,40 +9,22 @@ vi.mock('@/i18n-config/language', () => ({ getLanguage: () => 'en_US', })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ workspacePermissionKeys: [ 'tool.manage', 'credential.use', 'credential.create', 'credential.manage', ] as string[], - workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'), })) -vi.mock('@/context/account-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/permission-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/version-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom, -})) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => ({ - useAtomValue: (atom: unknown) => { - if (atom === mockAppContextState.workspacePermissionKeysAtom) - return mockAppContextState.workspacePermissionKeys - - throw new Error('Unexpected atom') - }, -})) + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: mockConsoleState.workspacePermissionKeys, + })) +}) const mockSetShowModelModal = vi.fn() vi.mock('@/context/modal-context', () => ({ @@ -256,7 +239,7 @@ describe('ProviderDetail', () => { ]) mockFetchCustomToolList.mockResolvedValue([]) mockFetchModelToolList.mockResolvedValue([]) - mockAppContextState.workspacePermissionKeys = [ + mockConsoleState.workspacePermissionKeys = [ 'tool.manage', 'credential.use', 'credential.create', @@ -435,7 +418,7 @@ describe('ProviderDetail', () => { }) it('renders custom tool details read-only without tool.manage', async () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] mockFetchCustomToolList.mockResolvedValue([ { name: 'custom-tool', @@ -534,7 +517,7 @@ describe('ProviderDetail', () => { }) it('renders workflow tool details read-only without tool.manage', async () => { - mockAppContextState.workspacePermissionKeys = [] + mockConsoleState.workspacePermissionKeys = [] render( { }) it('does not open setup credential drawer without credential.create', async () => { - mockAppContextState.workspacePermissionKeys = [ + mockConsoleState.workspacePermissionKeys = [ 'tool.manage', 'credential.use', 'credential.manage', @@ -689,7 +672,7 @@ describe('ProviderDetail', () => { }) it('opens authorized credential drawer as readonly with credential.use only', async () => { - mockAppContextState.workspacePermissionKeys = ['tool.manage', 'credential.use'] + mockConsoleState.workspacePermissionKeys = ['tool.manage', 'credential.use'] render( | null } -let appContextState: { +let consoleState: { isLoadingCurrentWorkspace: boolean currentWorkspace: { id?: string @@ -84,63 +85,33 @@ vi.mock('@/app/components/workflow/store/trigger-status', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - isLoadingCurrentWorkspace: appContextState.isLoadingCurrentWorkspace, - currentWorkspace: appContextState.currentWorkspace, - userProfile: appContextState.userProfile, - workspacePermissionKeys: appContextState.workspacePermissionKeys, +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ + isLoadingCurrentWorkspace: consoleState.isLoadingCurrentWorkspace, + currentWorkspace: consoleState.currentWorkspace, + userProfile: consoleState.userProfile, + workspacePermissionKeys: consoleState.workspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - isLoadingCurrentWorkspace: appContextState.isLoadingCurrentWorkspace, - currentWorkspace: appContextState.currentWorkspace, - userProfile: appContextState.userProfile, - workspacePermissionKeys: appContextState.workspacePermissionKeys, +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + isLoadingCurrentWorkspace: consoleState.isLoadingCurrentWorkspace, + currentWorkspace: consoleState.currentWorkspace, + userProfile: consoleState.userProfile, + workspacePermissionKeys: consoleState.workspacePermissionKeys, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - isLoadingCurrentWorkspace: appContextState.isLoadingCurrentWorkspace, - currentWorkspace: appContextState.currentWorkspace, - userProfile: appContextState.userProfile, - workspacePermissionKeys: appContextState.workspacePermissionKeys, +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ + isLoadingCurrentWorkspace: consoleState.isLoadingCurrentWorkspace, + currentWorkspace: consoleState.currentWorkspace, + userProfile: consoleState.userProfile, + workspacePermissionKeys: consoleState.workspacePermissionKeys, })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - isLoadingCurrentWorkspace: appContextState.isLoadingCurrentWorkspace, - currentWorkspace: appContextState.currentWorkspace, - userProfile: appContextState.userProfile, - workspacePermissionKeys: appContextState.workspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - isLoadingCurrentWorkspace: appContextState.isLoadingCurrentWorkspace, - currentWorkspace: appContextState.currentWorkspace, - userProfile: appContextState.userProfile, - workspacePermissionKeys: appContextState.workspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/next/navigation', () => ({ useSearchParams: () => ({ @@ -267,7 +238,7 @@ describe('WorkflowApp', () => { isLoading: false, fileUploadConfigResponse: { enabled: true }, } - appContextState = { + consoleState = { isLoadingCurrentWorkspace: false, currentWorkspace: { id: 'workspace-1' }, userProfile: { id: 'user-1' }, diff --git a/web/app/components/workflow-app/components/__tests__/workflow-main.spec.tsx b/web/app/components/workflow-app/components/__tests__/workflow-main.spec.tsx index da57e773d73..97a011df485 100644 --- a/web/app/components/workflow-app/components/__tests__/workflow-main.spec.tsx +++ b/web/app/components/workflow-app/components/__tests__/workflow-main.spec.tsx @@ -1,9 +1,10 @@ import type { ReactNode } from 'react' import type { WorkflowProps } from '@/app/components/workflow' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { useStore as useAppStore } from '@/app/components/app/store' import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' import { BlockEnum } from '@/app/components/workflow/types' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import WorkflowMain from '../workflow-main' const mockSetFeatures = vi.fn() @@ -76,6 +77,13 @@ type MockWorkflowWithInnerContextProps = Pick< children?: ReactNode } +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/app/components/base/features/hooks', () => ({ useFeaturesStore: () => ({ getState: () => ({ @@ -342,6 +350,14 @@ vi.mock('../workflow-children', () => ({ default: () =>
workflow-children
, })) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('WorkflowMain', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/workflow-app/hooks/__tests__/use-nodes-sync-draft.spec.ts b/web/app/components/workflow-app/hooks/__tests__/use-nodes-sync-draft.spec.ts index 1aadaa5f44e..d4cf9cd22ab 100644 --- a/web/app/components/workflow-app/hooks/__tests__/use-nodes-sync-draft.spec.ts +++ b/web/app/components/workflow-app/hooks/__tests__/use-nodes-sync-draft.spec.ts @@ -1,7 +1,7 @@ import { act } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderHookWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { BlockEnum } from '@/app/components/workflow/types' +import { renderHookWithConsoleQuery } from '@/test/console/query-data' import { useNodesSyncDraft } from '../use-nodes-sync-draft' const mockGetNodes = vi.fn() @@ -101,7 +101,7 @@ vi.mock('@/app/components/workflow-app/hooks', () => ({ })) const renderUseNodesSyncDraft = () => - renderHookWithSystemFeatures(() => useNodesSyncDraft(), { + renderHookWithConsoleQuery(() => useNodesSyncDraft(), { systemFeatures: { enable_collaboration_mode: isCollaborationEnabled }, }) diff --git a/web/app/components/workflow-app/hooks/__tests__/use-workflow-init.spec.ts b/web/app/components/workflow-app/hooks/__tests__/use-workflow-init.spec.ts index f572c8b6f62..32dbeda469d 100644 --- a/web/app/components/workflow-app/hooks/__tests__/use-workflow-init.spec.ts +++ b/web/app/components/workflow-app/hooks/__tests__/use-workflow-init.spec.ts @@ -1,6 +1,7 @@ -import { renderHook, waitFor } from '@testing-library/react' +import { waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { BlockEnum } from '@/app/components/workflow/types' +import { renderHook } from '@/test/console/render' import { AppACLPermission } from '@/utils/permission' import { useWorkflowInit } from '../use-workflow-init' @@ -45,53 +46,20 @@ vi.mock('@/app/components/app/store', () => ({ useStore: (selector: (state: typeof appStoreState) => T): T => selector(appStoreState), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['app.create_and_management'], })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: ['app.create_and_management'], })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: ['app.create_and_management'], - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: ['app.create_and_management'], - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-1' }, - workspacePermissionKeys: ['app.create_and_management'], - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('../use-workflow-template', () => ({ useWorkflowTemplate: () => ({ diff --git a/web/app/components/workflow/__tests__/selection-contextmenu.spec.tsx b/web/app/components/workflow/__tests__/selection-contextmenu.spec.tsx index 0f102ec82fc..39b0dd3a93c 100644 --- a/web/app/components/workflow/__tests__/selection-contextmenu.spec.tsx +++ b/web/app/components/workflow/__tests__/selection-contextmenu.spec.tsx @@ -23,42 +23,12 @@ const mockWorkspacePermissionKeys = vi.hoisted(() => ({ value: ['snippets.create_and_modify'] as string[], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/app/components/snippets/hooks/use-create-snippet', async () => { const React = await vi.importActual('react') diff --git a/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx b/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx index 49ec9dead7d..d2ddf008de8 100644 --- a/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx +++ b/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx @@ -145,6 +145,13 @@ const baseEdges = [ }, ] as unknown as Edge[] +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/next/dynamic', () => ({ default: () => () => null, })) @@ -494,6 +501,14 @@ function getPane(container: HTMLElement) { return pane } +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('Workflow edge event wiring', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/workflow/__tests__/workflow-test-env.tsx b/web/app/components/workflow/__tests__/workflow-test-env.tsx index c0966dd2701..ea3a84d0302 100644 --- a/web/app/components/workflow/__tests__/workflow-test-env.tsx +++ b/web/app/components/workflow/__tests__/workflow-test-env.tsx @@ -71,10 +71,12 @@ import type { Shape } from '../store/workflow' import type { WorkflowHistoryState } from '../store/workflow/history-slice' import type { Edge, Node, WorkflowRunningData } from '../types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, renderHook } from '@testing-library/react' import * as React from 'react' import ReactFlow, { ReactFlowProvider } from 'reactflow' -import { seedAppDslVersion, seedSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { seedAccountProfileQuery } from '@/test/console/account-profile' +import { createQueryClientWrapper } from '@/test/console/query-client' +import { seedAppDslVersion, seedSystemFeatures } from '@/test/console/query-data' +import { render, renderHook } from '@/test/console/render' import { WorkflowContext } from '../context' import { HooksStoreContext } from '../hooks-store/provider' import { createHooksStore } from '../hooks-store/store' @@ -166,11 +168,18 @@ function createWorkflowWrapper( defaultOptions: { queries: { retry: false, + gcTime: Infinity, + staleTime: Infinity, }, }, }) if (!externalQueryClient) seedSystemFeatures(queryClient) if (!externalQueryClient) seedAppDslVersion(queryClient) + if (!externalQueryClient) seedAccountProfileQuery(queryClient) + const QueryClientWrapper = externalQueryClient + ? ({ children }: { children: React.ReactNode }) => + React.createElement(QueryClientProvider, { client: queryClient }, children) + : createQueryClientWrapper(queryClient) return ({ children }: { children: React.ReactNode }) => { let inner: React.ReactNode = children @@ -180,8 +189,8 @@ function createWorkflowWrapper( } return React.createElement( - QueryClientProvider, - { client: queryClient }, + QueryClientWrapper, + null, React.createElement(WorkflowContext.Provider, { value: stores.store }, inner), ) } diff --git a/web/app/components/workflow/block-selector/__tests__/all-start-blocks.spec.tsx b/web/app/components/workflow/block-selector/__tests__/all-start-blocks.spec.tsx index 3fcd735d143..c1931ff2776 100644 --- a/web/app/components/workflow/block-selector/__tests__/all-start-blocks.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/all-start-blocks.spec.tsx @@ -2,13 +2,13 @@ import type { ReactElement } from 'react' import type { TriggerWithProvider } from '../types' import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks' import { CollectionType } from '@/app/components/tools/types' import { useGetLanguage, useLocale } from '@/context/i18n' import useTheme from '@/hooks/use-theme' import { useFeaturedTriggersRecommendations } from '@/service/use-plugins' import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins } from '@/service/use-triggers' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { Theme } from '@/types/app' import { useAvailableNodesMetaData } from '../../../workflow-app/hooks' import useNodes from '../../store/workflow/use-nodes' @@ -115,7 +115,7 @@ const createTriggerProvider = ( let enableMarketplaceForRender = false const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: enableMarketplaceForRender }, }) diff --git a/web/app/components/workflow/block-selector/__tests__/all-tools.spec.tsx b/web/app/components/workflow/block-selector/__tests__/all-tools.spec.tsx index 8fa92760e79..3cc4defb2f1 100644 --- a/web/app/components/workflow/block-selector/__tests__/all-tools.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/all-tools.spec.tsx @@ -1,11 +1,11 @@ import type { ReactElement } from 'react' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks' import { CollectionType } from '@/app/components/tools/types' import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { Theme } from '@/types/app' import AllTools from '../all-tools' import { createToolProvider } from './factories' @@ -46,7 +46,7 @@ const mockUseGetLanguage = vi.mocked(useGetLanguage) const mockUseTheme = vi.mocked(useTheme) const render = (ui: ReactElement, enableMarketplace = false) => - renderWithSystemFeatures(ui, { systemFeatures: { enable_marketplace: enableMarketplace } }) + renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: enableMarketplace } }) const createMarketplacePluginsMock = () => ({ plugins: [], diff --git a/web/app/components/workflow/block-selector/__tests__/data-sources.spec.tsx b/web/app/components/workflow/block-selector/__tests__/data-sources.spec.tsx index 08c4f2f7a97..1c4e1c97512 100644 --- a/web/app/components/workflow/block-selector/__tests__/data-sources.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/data-sources.spec.tsx @@ -2,12 +2,12 @@ import type { ReactElement } from 'react' import type { ToolWithProvider } from '../../types' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks' import { PluginCategoryEnum } from '@/app/components/plugins/types' import { CollectionType } from '@/app/components/tools/types' import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { Theme } from '@/types/app' import { BlockEnum } from '../../types' import DataSources from '../data-sources' @@ -30,7 +30,7 @@ const mockUseMarketplacePlugins = vi.mocked(useMarketplacePlugins) let enableMarketplaceForRender = false const render = (ui: ReactElement) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: enableMarketplaceForRender }, }) diff --git a/web/app/components/workflow/block-selector/__tests__/featured-tools.spec.tsx b/web/app/components/workflow/block-selector/__tests__/featured-tools.spec.tsx index 8e6a4712fe4..2fd5c63e1fb 100644 --- a/web/app/components/workflow/block-selector/__tests__/featured-tools.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/featured-tools.spec.tsx @@ -114,10 +114,7 @@ describe('FeaturedTools', () => { expect(trigger).toHaveAttribute('aria-expanded', 'true') expect(screen.getByText('Provider One')).toBeInTheDocument() - expect(globalThis.localStorage.setItem).toHaveBeenCalledWith( - 'workflow_tools_featured_collapsed', - 'false', - ) + expect(globalThis.localStorage.getItem('workflow_tools_featured_collapsed')).toBe('false') }) it('keeps the marketplace link and row actions keyboard reachable', async () => { diff --git a/web/app/components/workflow/block-selector/__tests__/featured-triggers.spec.tsx b/web/app/components/workflow/block-selector/__tests__/featured-triggers.spec.tsx index 9c9ebeaeeb4..c45b3ea24c6 100644 --- a/web/app/components/workflow/block-selector/__tests__/featured-triggers.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/featured-triggers.spec.tsx @@ -123,10 +123,7 @@ describe('FeaturedTriggers', () => { expect( screen.queryByRole('link', { name: 'workflow.tabs.noFeaturedTriggers' }), ).not.toBeInTheDocument() - expect(globalThis.localStorage.setItem).toHaveBeenCalledWith( - 'workflow_triggers_featured_collapsed', - 'true', - ) + expect(globalThis.localStorage.getItem('workflow_triggers_featured_collapsed')).toBe('true') }) it('should reveal installed providers in batches and then return to the initial list', async () => { diff --git a/web/app/components/workflow/block-selector/__tests__/tabs.spec.tsx b/web/app/components/workflow/block-selector/__tests__/tabs.spec.tsx index d56bdee5c2e..ad47522dde9 100644 --- a/web/app/components/workflow/block-selector/__tests__/tabs.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/tabs.spec.tsx @@ -1,12 +1,12 @@ import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { SelectorContent } from '../tabs' import { TabType } from '../types' const render = (ui: React.ReactElement) => - renderWithSystemFeatures(ui, { systemFeatures: { enable_marketplace: true } }) + renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: true } }) const { mockSetState, mockInvalidateBuiltInTools, mockToolsState } = vi.hoisted(() => ({ mockSetState: vi.fn(), diff --git a/web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx b/web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx index ecf26fed681..9b8ddf6c3a3 100644 --- a/web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx @@ -4,7 +4,6 @@ import type { Plugin } from '@/app/components/plugins/types' import type { Tool } from '@/app/components/tools/types' import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useTags } from '@/app/components/plugins/hooks' import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks' import { PluginCategoryEnum } from '@/app/components/plugins/types' @@ -23,6 +22,7 @@ import { useInvalidateAllMCPTools, useInvalidateAllWorkflowTools, } from '@/service/use-tools' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { Theme } from '@/types/app' import ToolPicker from '../tool-picker' @@ -63,42 +63,12 @@ vi.mock('@/config', async (importOriginal) => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/hooks/use-theme', () => ({ default: vi.fn(), @@ -360,7 +330,7 @@ const mcpTools = [ ] const renderToolPicker = (props: Partial> = {}) => { - return renderWithSystemFeatures( + return renderWithConsoleQuery( open-picker} diff --git a/web/app/components/workflow/collaboration/hooks/__tests__/use-collaboration.spec.ts b/web/app/components/workflow/collaboration/hooks/__tests__/use-collaboration.spec.ts index 169b45cc798..828d8275e8a 100644 --- a/web/app/components/workflow/collaboration/hooks/__tests__/use-collaboration.spec.ts +++ b/web/app/components/workflow/collaboration/hooks/__tests__/use-collaboration.spec.ts @@ -1,6 +1,6 @@ import type { CursorPosition, NodePanelPresenceMap, OnlineUser } from '../../types/collaboration' import { waitFor } from '@testing-library/react' -import { renderHookWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderHookWithConsoleQuery } from '@/test/console/query-data' import { useCollaboration } from '../use-collaboration' type HookReactFlowStore = NonNullable[1]> @@ -104,7 +104,7 @@ describe('useCollaboration', () => { const reactFlowStore: HookReactFlowStore = { getState: vi.fn(), } - const { result, unmount } = renderHookWithSystemFeatures( + const { result, unmount } = renderHookWithConsoleQuery( () => useCollaboration('app-1', reactFlowStore), { systemFeatures: { enable_collaboration_mode: isCollaborationEnabled }, @@ -157,7 +157,7 @@ describe('useCollaboration', () => { it('does not connect or start cursor tracking when collaboration is disabled', async () => { isCollaborationEnabled = false - const { result } = renderHookWithSystemFeatures(() => useCollaboration('app-1'), { + const { result } = renderHookWithConsoleQuery(() => useCollaboration('app-1'), { systemFeatures: { enable_collaboration_mode: isCollaborationEnabled }, }) diff --git a/web/app/components/workflow/comment/comment-icon.spec.tsx b/web/app/components/workflow/comment/comment-icon.spec.tsx index 8c6144b616f..ba07479f082 100644 --- a/web/app/components/workflow/comment/comment-icon.spec.tsx +++ b/web/app/components/workflow/comment/comment-icon.spec.tsx @@ -1,12 +1,13 @@ import type { WorkflowCommentList } from '@/app/components/workflow/comment/types' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { CommentIcon } from './comment-icon' type Position = { x: number; y: number } let mockUserId = 'user-1' -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1', name: 'User', @@ -29,62 +30,16 @@ vi.mock('reactflow', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - ...mockAppContextState, +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ + ...mockConsoleState, userProfile: { - ...mockAppContextState.userProfile, + ...mockConsoleState.userProfile, id: mockUserId, }, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - ...mockAppContextState, - userProfile: { - ...mockAppContextState.userProfile, - id: mockUserId, - }, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - ...mockAppContextState, - userProfile: { - ...mockAppContextState.userProfile, - id: mockUserId, - }, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - ...mockAppContextState, - userProfile: { - ...mockAppContextState.userProfile, - id: mockUserId, - }, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - ...mockAppContextState, - userProfile: { - ...mockAppContextState.userProfile, - id: mockUserId, - }, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/app/components/base/user-avatar-list', () => ({ UserAvatarList: ({ users }: { users: Array<{ id: string }> }) => ( diff --git a/web/app/components/workflow/comment/comment-input.spec.tsx b/web/app/components/workflow/comment/comment-input.spec.tsx index bcc19243b1e..8d498d3fc63 100644 --- a/web/app/components/workflow/comment/comment-input.spec.tsx +++ b/web/app/components/workflow/comment/comment-input.spec.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render } from '@/test/console/render' import { CommentInput } from './comment-input' type MentionInputProps = { @@ -17,7 +18,7 @@ const stableT = (key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key let mentionInputProps: MentionInputProps | null = null -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1', name: 'Alice', @@ -34,31 +35,9 @@ vi.mock('react-i18next', async () => { } }) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('@langgenius/dify-ui/avatar', () => ({ diff --git a/web/app/components/workflow/comment/thread.spec.tsx b/web/app/components/workflow/comment/thread.spec.tsx index 11b776b1a63..bc8866cbb15 100644 --- a/web/app/components/workflow/comment/thread.spec.tsx +++ b/web/app/components/workflow/comment/thread.spec.tsx @@ -1,12 +1,13 @@ import type { WorkflowCommentDetail } from '@/app/components/workflow/comment/types' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { render } from '@/test/console/render' import { CommentThread } from './thread' const mockSetCommentPreviewHovering = vi.hoisted(() => vi.fn()) const mockFlowToScreenPosition = vi.hoisted(() => vi.fn(({ x, y }: { x: number; y: number }) => ({ x, y })), ) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1', name: 'Alice', @@ -33,31 +34,9 @@ vi.mock('@/hooks/use-format-time-from-now', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('reactflow', () => ({ diff --git a/web/app/components/workflow/header/__tests__/header-in-restoring.spec.tsx b/web/app/components/workflow/header/__tests__/header-in-restoring.spec.tsx index 31eee0043ec..a08079b708e 100644 --- a/web/app/components/workflow/header/__tests__/header-in-restoring.spec.tsx +++ b/web/app/components/workflow/header/__tests__/header-in-restoring.spec.tsx @@ -13,38 +13,16 @@ const mockHandleLoadBackupDraft = vi.fn() const mockHandleRefreshWorkflowDraft = vi.fn() let mockPlanType = Plan.professional let mockEnableBilling = true -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: '', name: '', }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('@/context/provider-context', () => ({ diff --git a/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx b/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx index b07623c377c..cf999af8fcd 100644 --- a/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx +++ b/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx @@ -25,38 +25,16 @@ const mockViewHistory = vi.fn() let mockNodesReadOnly = false let mockTheme: 'light' | 'dark' = 'light' -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: '', name: '', }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('reactflow', () => ({ diff --git a/web/app/components/workflow/hooks/__tests__/use-node-plugin-installation.spec.ts b/web/app/components/workflow/hooks/__tests__/use-node-plugin-installation.spec.ts index 384108cbe2b..c2444e4c4d5 100644 --- a/web/app/components/workflow/hooks/__tests__/use-node-plugin-installation.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-node-plugin-installation.spec.ts @@ -15,42 +15,12 @@ const mockInvalidateTriggers = vi.fn() const mockInvalidDataSourceList = vi.fn() let mockWorkspacePermissionKeys = ['plugin.install'] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/use-tools', () => ({ useAllBuiltInTools: (enabled: boolean) => mockBuiltInTools(enabled), diff --git a/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts b/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts index a02b4397fe3..88c0d66079c 100644 --- a/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts @@ -3,7 +3,7 @@ import type { WorkflowCommentList, } from '@/app/components/workflow/comment/types' import { act, waitFor } from '@testing-library/react' -import { createTestQueryClient, seedSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { createConsoleQueryClient, seedSystemFeatures } from '@/test/console/query-data' import { renderWorkflowHook } from '../../__tests__/workflow-test-env' import { ControlMode } from '../../types' import { useWorkflowComment } from '../use-workflow-comment' @@ -33,7 +33,7 @@ const commentsUpdateState = vi.hoisted(() => ({ const globalFeatureState = vi.hoisted(() => ({ enableCollaboration: true, })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1', name: 'Alice', @@ -54,31 +54,9 @@ vi.mock('@/next/navigation', () => ({ useParams: () => ({ appId: 'app-1' }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('@/service/client', () => ({ @@ -173,7 +151,7 @@ const baseCommentDetail = (): WorkflowCommentDetail => ({ }) const createSeededQueryClient = () => { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() seedSystemFeatures(queryClient, { enable_collaboration_mode: globalFeatureState.enableCollaboration, }) diff --git a/web/app/components/workflow/nodes/_base/__tests__/node.spec.tsx b/web/app/components/workflow/nodes/_base/__tests__/node.spec.tsx index 5612db1506a..b4fd610e28b 100644 --- a/web/app/components/workflow/nodes/_base/__tests__/node.spec.tsx +++ b/web/app/components/workflow/nodes/_base/__tests__/node.spec.tsx @@ -11,7 +11,7 @@ const mockHandleNodeIterationChildSizeChange = vi.fn() const mockHandleNodeLoopChildSizeChange = vi.fn() const mockUseNodeResizeObserver = vi.fn() const mockUseCollaboration = vi.fn() -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1', name: 'User', @@ -21,31 +21,9 @@ const mockAppContextState = vi.hoisted(() => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('@/app/components/workflow/hooks', () => ({ @@ -305,7 +283,9 @@ describe('BaseNode', () => { ) expect(mockHandleNodeLoopChildSizeChange).toHaveBeenCalledWith('node-2') - expect(mockUseNodeResizeObserver).toHaveBeenCalledTimes(2) + expect(mockUseNodeResizeObserver).toHaveBeenCalledWith( + expect.objectContaining({ enabled: true }), + ) }) it('should keep viewer avatars outside the truncated title area', () => { diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/email-configure-modal.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/email-configure-modal.spec.tsx index af818d41ab3..1b7b3300cc5 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/email-configure-modal.spec.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/email-configure-modal.spec.tsx @@ -1,9 +1,10 @@ import type { EmailConfig } from '../../../types' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' +import { render } from '@/test/console/render' import EmailConfigureModal from '../email-configure-modal' const mockToastError = vi.hoisted(() => vi.fn()) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { email: 'owner@example.com', }, @@ -15,31 +16,9 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('../mail-body-input', () => ({ diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-item.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-item.spec.tsx index 9217db3e71c..e98e473983c 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-item.spec.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-item.spec.tsx @@ -1,7 +1,8 @@ import type { EmailConfig, FormInputItem } from '../../../types' import type { Node, NodeOutPutVar } from '@/app/components/workflow/types' -import { fireEvent, render, screen, within } from '@testing-library/react' +import { fireEvent, screen, within } from '@testing-library/react' import { InputVarType } from '@/app/components/workflow/types' +import { render } from '@/test/console/render' import { DeliveryMethodType } from '../../../types' import DeliveryMethodItem from '../method-item' @@ -20,37 +21,15 @@ type TestEmailSenderProps = { const mockEmailConfigureModal = vi.hoisted(() => vi.fn()) const mockTestEmailSender = vi.hoisted(() => vi.fn()) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { email: 'owner@example.com', }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('../email-configure-modal', () => ({ diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/test-email-sender.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/test-email-sender.spec.tsx index 36da6e37606..e1c4628851c 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/test-email-sender.spec.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/test-email-sender.spec.tsx @@ -9,13 +9,14 @@ import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types' import type { App, AppSSO } from '@/types/app' import { toast } from '@langgenius/dify-ui/toast' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useStore as useAppStore } from '@/app/components/app/store' import { HooksStoreContext } from '@/app/components/workflow/hooks-store/provider' import { createHooksStore } from '@/app/components/workflow/hooks-store/store' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types' +import { render } from '@/test/console/render' import EmailSenderModal from '../test-email-sender' vi.mock('@langgenius/dify-ui/toast', () => ({ @@ -24,7 +25,7 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ }, })) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1', email: 'owner@example.com', @@ -36,31 +37,13 @@ const mockAppContextState = vi.hoisted(() => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) type RecordedRequest = { diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/__tests__/index.spec.tsx index 3dc9c79a9f7..babafbc8f6d 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/__tests__/index.spec.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/__tests__/index.spec.tsx @@ -1,11 +1,12 @@ -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' +import { render } from '@/test/console/render' import { withSelectorKey } from '@/test/i18n-mock' import Recipient from '../index' const mockUseTranslation = vi.hoisted(() => vi.fn()) -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) const mockUseMembers = vi.hoisted(() => vi.fn()) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { email: 'owner@example.com' }, currentWorkspace: { name: "Dify's Lab" }, })) @@ -14,31 +15,13 @@ vi.mock('react-i18next', () => ({ useTranslation: () => mockUseTranslation(), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleState) }) vi.mock('@/service/use-common', () => ({ @@ -99,7 +82,7 @@ describe('Recipient', () => { (key: string, options?: { workspaceName?: string }) => options?.workspaceName ?? key, ), }) - mockUseAppContext.mockReturnValue(mockAppContextState) + mockConsoleStateReader.mockReturnValue(mockConsoleState) mockUseMembers.mockReturnValue({ data: { accounts: [ diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/__tests__/integration.spec.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/__tests__/integration.spec.tsx index 11b2de37e56..52ec8f1625e 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/__tests__/integration.spec.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/__tests__/integration.spec.tsx @@ -1,13 +1,12 @@ import type { ComparisonOperator, MetadataFilteringCondition, MetadataShape } from '../types' import type { DataSet, MetadataInDoc } from '@/models/datasets' -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import { fireEvent, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useEffect, useRef } from 'react' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' -import { - AccountProfileQueryProvider, - createAccountProfileQueryClient, -} from '@/test/account-profile-query' +import { createAccountProfileQueryClient } from '@/test/console/account-profile' +import { QueryClientTestProvider } from '@/test/console/query-provider' +import { render } from '@/test/console/render' import { RETRIEVE_METHOD, RETRIEVE_TYPE } from '@/types/app' import { DatasetACLPermission, getDatasetACLCapabilities } from '@/utils/permission' import { DatasetsDetailContext } from '../../../datasets-detail-store/provider' @@ -132,36 +131,18 @@ const createCondition = ( ...overrides, }) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1' }, workspacePermissionKeys: [] as string[], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => mockConsoleState) }) vi.mock('@/utils/permission', () => ({ @@ -728,7 +709,7 @@ describe('knowledge-retrieval path', () => { const queryClient = createAccountProfileQueryClient({ timezone: 'UTC' }) const { container } = render( - +
{ onUpdateCondition={onUpdateCondition} />
-
, + , ) await user.click(screen.getAllByRole('button', { name: /contains/i })[0]!) diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/__tests__/import-from-tool.spec.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/__tests__/import-from-tool.spec.tsx index b66eafe4ee3..e7c7b52e7d4 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/__tests__/import-from-tool.spec.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/__tests__/import-from-tool.spec.tsx @@ -2,13 +2,13 @@ import type { ToolParameter } from '@/app/components/tools/types' import type { ToolWithProvider } from '@/app/components/workflow/types' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { createSystemFeaturesWrapper } from '@/__tests__/utils/mock-system-features' import { CollectionType } from '@/app/components/tools/types' import { renderWorkflowComponent } from '@/app/components/workflow/__tests__/workflow-test-env' import { createTool, createToolProvider, } from '@/app/components/workflow/block-selector/__tests__/factories' +import { createConsoleQueryWrapper } from '@/test/console/query-data' import ImportFromTool from '../import-from-tool' vi.mock('reactflow', () => ({ @@ -70,10 +70,10 @@ const createToolParameter = (overrides: Partial = {}): ToolParame }) const renderImportFromTool = (ui: React.ReactElement) => { - const { wrapper: SystemFeaturesWrapper } = createSystemFeaturesWrapper({ + const { wrapper: ConsoleQueryWrapper } = createConsoleQueryWrapper({ systemFeatures: { enable_marketplace: false }, }) - return renderWorkflowComponent({ui}, { + return renderWorkflowComponent({ui}, { hooksStoreProps: { availableNodesMetaData: { nodes: [] }, }, diff --git a/web/app/components/workflow/nodes/trigger-schedule/__tests__/use-config.spec.ts b/web/app/components/workflow/nodes/trigger-schedule/__tests__/use-config.spec.ts index b94b11b12a9..783b4276f7d 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/__tests__/use-config.spec.ts +++ b/web/app/components/workflow/nodes/trigger-schedule/__tests__/use-config.spec.ts @@ -2,11 +2,11 @@ import type { ScheduleTriggerNodeType } from '../types' import { renderHook } from '@testing-library/react' import { useNodesReadOnly } from '@/app/components/workflow/hooks' import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' -import { createAccountProfileQueryWrapper } from '@/test/account-profile-query' +import { createAccountProfileQueryWrapper } from '@/test/console/account-profile' import { BlockEnum } from '../../../types' import useConfig from '../use-config' -const mockUseAppContext = vi.hoisted(() => vi.fn()) +const mockConsoleStateReader = vi.hoisted(() => vi.fn()) vi.mock('@/app/components/workflow/hooks', () => ({ useNodesReadOnly: vi.fn(), @@ -42,7 +42,7 @@ describe('trigger-schedule/use-config', () => { beforeEach(() => { vi.clearAllMocks() mockUseNodesReadOnly.mockReturnValue({ nodesReadOnly: false, getNodesReadOnly: () => false }) - mockUseAppContext.mockReturnValue({ + mockConsoleStateReader.mockReturnValue({ userProfile: { timezone: 'Asia/Shanghai' }, }) mockUseNodeCrud.mockReturnValue({ diff --git a/web/app/components/workflow/nodes/variable-assigner/components/__tests__/var-group-item.spec.tsx b/web/app/components/workflow/nodes/variable-assigner/components/__tests__/var-group-item.spec.tsx index 2a922b9c79d..63a20e43148 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/__tests__/var-group-item.spec.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/__tests__/var-group-item.spec.tsx @@ -15,6 +15,10 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ }, })) +vi.mock('@/app/components/workflow/nodes/_base/components/variable/var-reference-picker', () => ({ + default: () => null, +})) + const mockToast = vi.mocked(toast) const createPayload = () => ({ diff --git a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/__tests__/index.spec.tsx b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/__tests__/index.spec.tsx index 4344b20328b..000dae9806f 100644 --- a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/__tests__/index.spec.tsx +++ b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/__tests__/index.spec.tsx @@ -20,8 +20,19 @@ const StoreProbe = ({ onReady }: { onReady?: (store: NoteEditorStore) => void }) } describe('LinkEditorPlugin', () => { + let anchor: HTMLAnchorElement + let portalRoot: HTMLDivElement + beforeEach(() => { vi.clearAllMocks() + anchor = document.createElement('a') + portalRoot = document.createElement('div') + document.body.append(anchor, portalRoot) + }) + + afterEach(() => { + anchor.remove() + portalRoot.remove() }) // Without an anchor element the plugin should stay hidden. @@ -43,7 +54,7 @@ describe('LinkEditorPlugin', () => { render( (store = instance)} /> - + , ) @@ -53,7 +64,7 @@ describe('LinkEditorPlugin', () => { act(() => { store!.setState({ - linkAnchorElement: document.createElement('a'), + linkAnchorElement: anchor, linkOperatorShow: false, selectedLinkUrl: 'https://example.com', }) diff --git a/web/app/components/workflow/operator/__tests__/zoom-in-out.spec.tsx b/web/app/components/workflow/operator/__tests__/zoom-in-out.spec.tsx index d3762e11841..c0f2f112616 100644 --- a/web/app/components/workflow/operator/__tests__/zoom-in-out.spec.tsx +++ b/web/app/components/workflow/operator/__tests__/zoom-in-out.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, screen, within } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderWithConsoleQuery } from '@/test/console/query-data' import ZoomInOut from '../zoom-in-out' const { @@ -52,7 +52,7 @@ vi.mock('../tip-popup', () => ({ })) const renderZoomInOut = (ui: React.ReactElement = ) => - renderWithSystemFeatures(ui, { + renderWithConsoleQuery(ui, { systemFeatures: { enable_collaboration_mode: collaborationEnabled }, }) diff --git a/web/app/components/workflow/panel/__tests__/human-input-form-list.spec.tsx b/web/app/components/workflow/panel/__tests__/human-input-form-list.spec.tsx index d21a8814316..b22057985aa 100644 --- a/web/app/components/workflow/panel/__tests__/human-input-form-list.spec.tsx +++ b/web/app/components/workflow/panel/__tests__/human-input-form-list.spec.tsx @@ -1,5 +1,5 @@ import type { HumanInputFormData } from '@/types/workflow' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { CUSTOM_NODE } from '@/app/components/workflow/constants' import { @@ -7,6 +7,7 @@ import { UserActionButtonType, } from '@/app/components/workflow/nodes/human-input/types' import { InputVarType } from '@/app/components/workflow/types' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import HumanInputFormList from '../human-input-form-list' const mockNodes: Array<{ diff --git a/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx b/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx index ce0759f27a5..3ac8345f50c 100644 --- a/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx +++ b/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx @@ -1,5 +1,6 @@ import type { WorkflowCommentList } from '@/app/components/workflow/comment/types' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { render } from '@/test/console/render' import CommentsPanel from '../index' const mockHandleCommentIconClick = vi.hoisted(() => vi.fn()) @@ -7,7 +8,7 @@ const mockHandleCommentResolve = vi.hoisted(() => vi.fn()) const mockSetActiveCommentId = vi.hoisted(() => vi.fn()) const mockSetControlMode = vi.hoisted(() => vi.fn()) const mockSetShowResolvedComments = vi.hoisted(() => vi.fn()) -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'user-1' }, })) @@ -64,31 +65,9 @@ vi.mock('@/hooks/use-format-time-from-now', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('@/app/components/workflow/store', () => ({ diff --git a/web/app/components/workflow/panel/version-history-panel/__tests__/index.spec.tsx b/web/app/components/workflow/panel/version-history-panel/__tests__/index.spec.tsx index 12993f4a7cb..5c9abb58404 100644 --- a/web/app/components/workflow/panel/version-history-panel/__tests__/index.spec.tsx +++ b/web/app/components/workflow/panel/version-history-panel/__tests__/index.spec.tsx @@ -1,8 +1,9 @@ import type { Shape } from '../../../store' import type { VersionHistory } from '@/types/workflow' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { useEffect, useRef } from 'react' import { Plan } from '@/app/components/billing/type' +import { render } from '@/test/console/render' import { VersionHistoryContextMenuOptions, WorkflowVersion } from '../../../types' const mockHandleRestoreFromPublishedWorkflow = vi.fn() @@ -18,7 +19,7 @@ const mockEmitRestoreComplete = vi.fn() const mockEmitWorkflowUpdate = vi.fn() let mockPlanType = Plan.professional let mockEnableBilling = true -const mockAppContextState = vi.hoisted(() => ({ +const mockConsoleState = vi.hoisted(() => ({ userProfile: { id: 'test-user-id', name: 'Test User', @@ -68,31 +69,9 @@ type MockVersionHistoryItemProps = { handleClickActionMenuItem: (operation: VersionHistoryContextMenuOptions) => void } -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => mockConsoleState) }) vi.mock('@/context/provider-context', () => ({ diff --git a/web/app/components/workflow/run/__tests__/index.spec.tsx b/web/app/components/workflow/run/__tests__/index.spec.tsx index f27c53d3ab8..5c3beb7d909 100644 --- a/web/app/components/workflow/run/__tests__/index.spec.tsx +++ b/web/app/components/workflow/run/__tests__/index.spec.tsx @@ -21,6 +21,11 @@ vi.mock('@/service/log', () => ({ fetchTracingList: (...args: unknown[]) => mockFetchTracingList(...args), })) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'account-1' } })) +}) + vi.mock('@langgenius/dify-ui/toast', () => ({ toast: { error: (...args: unknown[]) => mockToastError(...args), diff --git a/web/app/components/workflow/run/agent-log/__tests__/agent-log-item.spec.tsx b/web/app/components/workflow/run/agent-log/__tests__/agent-log-item.spec.tsx index c86be039442..2670dd8074f 100644 --- a/web/app/components/workflow/run/agent-log/__tests__/agent-log-item.spec.tsx +++ b/web/app/components/workflow/run/agent-log/__tests__/agent-log-item.spec.tsx @@ -1,6 +1,7 @@ import type { AgentLogItemWithChildren } from '@/types/workflow' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import AgentLogItem from '../agent-log-item' const createLogItem = ( @@ -19,6 +20,13 @@ const createLogItem = ( ...overrides, }) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + describe('AgentLogItem', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/workflow/run/agent-log/__tests__/agent-result-panel.spec.tsx b/web/app/components/workflow/run/agent-log/__tests__/agent-result-panel.spec.tsx index 9719c92f3df..7ec962caba4 100644 --- a/web/app/components/workflow/run/agent-log/__tests__/agent-result-panel.spec.tsx +++ b/web/app/components/workflow/run/agent-log/__tests__/agent-result-panel.spec.tsx @@ -1,6 +1,7 @@ import type { AgentLogItemWithChildren } from '@/types/workflow' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import AgentResultPanel from '../agent-result-panel' const createLogItem = ( @@ -16,6 +17,13 @@ const createLogItem = ( ...overrides, }) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + describe('AgentResultPanel', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/install/installForm.spec.tsx b/web/app/install/installForm.spec.tsx index 086c3c26b13..f60a9488f5f 100644 --- a/web/app/install/installForm.spec.tsx +++ b/web/app/install/installForm.spec.tsx @@ -1,13 +1,13 @@ import type { ReactElement } from 'react' import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { fetchInitValidateStatus, fetchSetupStatus, login, setup } from '@/service/common' import { expectLoadingButton } from '@/test/button' +import { renderWithConsoleQuery } from '@/test/console/query-data' import { encryptPassword } from '@/utils/encryption' import InstallForm from './installForm' -const render = (ui: ReactElement) => renderWithSystemFeatures(ui) +const render = (ui: ReactElement) => renderWithConsoleQuery(ui) const mockPush = vi.fn() const mockReplace = vi.fn() diff --git a/web/app/signup/components/input-mail.spec.tsx b/web/app/signup/components/input-mail.spec.tsx index 9b51ffc5fb7..a22d4b50983 100644 --- a/web/app/signup/components/input-mail.spec.tsx +++ b/web/app/signup/components/input-mail.spec.tsx @@ -1,10 +1,10 @@ import type { MockedFunction } from 'vitest' import { fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' -import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { useLocale } from '@/context/i18n' import { useSearchParams } from '@/next/navigation' import { useSendMail } from '@/service/use-common' +import { renderWithConsoleQuery } from '@/test/console/query-data' import Form from './input-mail' const mockSubmitMail = vi.fn() @@ -60,7 +60,7 @@ const renderForm = ({ mutateAsync: mockSubmitMail, isPending, } as unknown as UseSendMailResult) - return renderWithSystemFeatures(
, { + return renderWithConsoleQuery(, { systemFeatures: { branding: { enabled: brandingEnabled } }, }) } diff --git a/web/context/__tests__/console-bootstrap.spec.tsx b/web/context/__tests__/console-bootstrap.spec.tsx index 4fe4eac66ea..0bf24e4c469 100644 --- a/web/context/__tests__/console-bootstrap.spec.tsx +++ b/web/context/__tests__/console-bootstrap.spec.tsx @@ -279,7 +279,7 @@ function TestQueryClientHydrator({ return children } -function createTestQueryClient() { +function createConsoleQueryClient() { return new QueryClient({ defaultOptions: { queries: { @@ -291,7 +291,7 @@ function createTestQueryClient() { } function renderConsoleBootstrap() { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() queryClient.setQueryData(['user-profile'], mockUserProfileResponseState.data) queryClient.setQueryData(['system-features'], mockSystemFeaturesState.data) diff --git a/web/context/modal-context.test.tsx b/web/context/modal-context.test.tsx index b4087e8783a..60eb1d87115 100644 --- a/web/context/modal-context.test.tsx +++ b/web/context/modal-context.test.tsx @@ -51,39 +51,11 @@ vi.mock('@/context/provider-context', () => ({ useProviderContext: () => mockUseProviderContext(), })) -const mockUseAppContext = vi.fn() +const mockConsoleStateReader = vi.fn() -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockUseAppContext()) -}) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockUseAppContext()) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockUseAppContext()) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockUseAppContext()) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => mockUseAppContext()) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => mockConsoleStateReader()) }) type DefaultPlanShape = typeof defaultPlan @@ -151,11 +123,11 @@ const PreferencesOpener = () => { describe('ModalContextProvider trigger events limit modal', () => { beforeEach(() => { - mockUseAppContext.mockReset() + mockConsoleStateReader.mockReset() mockUseProviderContext.mockReset() mockSetEducationVerifying.mockReset() window.localStorage.clear() - mockUseAppContext.mockReturnValue({ + mockConsoleStateReader.mockReturnValue({ currentWorkspace: { id: 'workspace-1', }, diff --git a/web/features/agent-v2/agent-detail/access/components/__tests__/access-surface-cards.spec.tsx b/web/features/agent-v2/agent-detail/access/components/__tests__/access-surface-cards.spec.tsx index ffc9db412e5..cfe266fd231 100644 --- a/web/features/agent-v2/agent-detail/access/components/__tests__/access-surface-cards.spec.tsx +++ b/web/features/agent-v2/agent-detail/access/components/__tests__/access-surface-cards.spec.tsx @@ -1,8 +1,9 @@ import type { AgentAppDetailWithSite } from '@dify/contracts/api/console/agent/types.gen' import type React from 'react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen, waitFor, within } from '@testing-library/react' +import { screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import { ServiceApiAccessCard } from '../service-api-access-card' import { WebAppAccessCard } from '../web-app-access-card' @@ -43,10 +44,9 @@ vi.mock('@/app/components/base/chat/embedded-chatbot/theme/theme-context', () => }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'user-1' }, currentWorkspace: { id: 'workspace-1' }, workspacePermissionKeys: ['app.acl.edit'], @@ -61,10 +61,9 @@ vi.mock('@/context/account-state', async (importOriginal) => { }, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ userProfile: { id: 'user-1' }, currentWorkspace: { id: 'workspace-1' }, workspacePermissionKeys: ['app.acl.edit'], @@ -79,10 +78,9 @@ vi.mock('@/context/workspace-state', async (importOriginal) => { }, })) }) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ userProfile: { id: 'user-1' }, currentWorkspace: { id: 'workspace-1' }, workspacePermissionKeys: ['app.acl.edit'], @@ -97,10 +95,9 @@ vi.mock('@/context/permission-state', async (importOriginal) => { }, })) }) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/version-state', async () => { + const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') + return createVersionStateModuleMock(() => ({ userProfile: { id: 'user-1' }, currentWorkspace: { id: 'workspace-1' }, workspacePermissionKeys: ['app.acl.edit'], @@ -115,31 +112,6 @@ vi.mock('@/context/version-state', async (importOriginal) => { }, })) }) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { id: 'user-1' }, - currentWorkspace: { id: 'workspace-1' }, - workspacePermissionKeys: ['app.acl.edit'], - langGeniusVersionInfo: { - current_env: 'PRODUCTION', - current_version: '', - latest_version: '', - version: '', - release_date: '', - release_notes: '', - can_auto_update: false, - }, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/client', () => ({ consoleQuery: { @@ -255,14 +227,14 @@ function createAgent(overrides: Partial = {}): AgentAppD } function renderWithQueryClient(ui: React.ReactElement) { - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() render({ui}) return queryClient } -function createTestQueryClient() { +function createConsoleQueryClient() { return new QueryClient({ defaultOptions: { queries: { @@ -561,7 +533,7 @@ describe('Agent access surface cards', () => { const agentWithoutSite = createAgent({ site: null, }) - const queryClient = createTestQueryClient() + const queryClient = createConsoleQueryClient() const { rerender } = render( diff --git a/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx index 75e288181c2..c194d4bd05a 100644 --- a/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react' import type { PromptEditorProps } from '@/app/components/base/prompt-editor' import type { AgentTool } from '@/features/agent-v2/agent-composer/form-state' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { createStore, Provider as JotaiProvider } from 'jotai' import { API_PREFIX } from '@/config' @@ -10,6 +10,8 @@ import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store import { agentComposerKnowledgeRetrievalsAtom } from '@/features/agent-v2/agent-composer/store-modules/knowledge' import { agentComposerPromptAtom } from '@/features/agent-v2/agent-composer/store-modules/prompt' import { agentComposerToolsAtom } from '@/features/agent-v2/agent-composer/store-modules/tools' +import { render } from '@/test/console/render' +import { seedRegisteredConsoleStateFixture } from '@/test/console/state-fixture' import { AgentPromptEditor } from '../orchestrate/prompt-editor' import { AgentPromptSlashMenu } from '../orchestrate/prompt-editor/slash' @@ -153,48 +155,12 @@ vi.mock('@/context/i18n', () => ({ useDocLink: () => 'https://docs.example.com', })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ currentWorkspace: { id: 'workspace-123' }, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-123' }, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-123' }, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-123' }, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - currentWorkspace: { id: 'workspace-123' }, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/use-tools', () => ({ useAllBuiltInTools: () => ({ data: mockBuiltInTools }), @@ -247,6 +213,7 @@ const renderAgentPromptEditor = ( draftOverrides: Partial = {}, ) => { const store = createStore() + seedRegisteredConsoleStateFixture(store) store.set(agentComposerDraftAtom, { ...promptEditorDraft, ...draftOverrides, diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/__tests__/index.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/__tests__/index.spec.tsx index e0e17aa0349..a39b96f55d8 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/__tests__/index.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen, within } from '@testing-library/react' +import { fireEvent, screen, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useAtomValue } from 'jotai' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -10,9 +10,17 @@ import { defaultAgentSoulConfigFormState } from '@/features/agent-v2/agent-compo import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider' import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store' import { RerankingModeEnum } from '@/models/datasets' +import { renderWithAccountProfile as render } from '@/test/console/account-profile' import { AgentOrchestrateReadOnlyContext } from '../../read-only-context' import { AgentKnowledgeRetrieval } from '../index' +vi.mock('@/context/workspace-state', async () => { + const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') + return createWorkspaceStateModuleMock(() => ({ + currentWorkspace: { id: 'workspace-1' }, + })) +}) + vi.mock('@/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset', () => ({ default: function MockAddKnowledge({ onChange, @@ -126,6 +134,14 @@ function getDialogNameEditButton(dialog: HTMLElement) { }) } +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: [], + })) +}) + describe('AgentKnowledgeRetrieval', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/__tests__/index.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/__tests__/index.spec.tsx index 063cd5a82cd..9e8f8184dbb 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/__tests__/index.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/__tests__/index.spec.tsx @@ -34,6 +34,13 @@ vi.mock('@/app/components/workflow/block-selector/tool-picker', () => ({ ToolPickerContent: () =>
Mock tool picker
, })) +vi.mock('@/context/account-state', async () => { + const { atom } = await vi.importActual('jotai') + return { + userProfileIdAtom: atom('user-1'), + } +}) + vi.mock('@/app/components/workflow/block-icon', () => ({ default: ({ toolIcon }: { toolIcon?: string | { content: string; background: string } }) => ( diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/chat.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/chat.spec.tsx index 9d37d10a09c..beab9ec0afd 100644 --- a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/chat.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/chat.spec.tsx @@ -1,13 +1,15 @@ import type { ComponentProps, ReactNode } from 'react' import type { SpeechToTextTarget } from '@/app/components/base/voice-input/types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, screen, waitFor } from '@testing-library/react' import { createStore, Provider as JotaiProvider } from 'jotai' import { useState } from 'react' import { SupportUploadFileTypes } from '@/app/components/workflow/types' import { agentComposerModelAtom } from '@/features/agent-v2/agent-composer/store-modules/model' import { agentComposerPromptAtom } from '@/features/agent-v2/agent-composer/store-modules/prompt' import { consoleQuery } from '@/service/client' +import { render } from '@/test/console/render' +import { seedRegisteredConsoleStateFixture } from '@/test/console/state-fixture' import { TransferMethod } from '@/types/app' import { AgentChatRuntime } from '../chat-runtime' @@ -125,63 +127,15 @@ vi.mock('@/app/components/base/chat/chat/hooks', () => ({ ), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { avatar_url: '', name: 'User', }, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - avatar_url: '', - name: 'User', - }, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - avatar_url: '', - name: 'User', - }, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - avatar_url: '', - name: 'User', - }, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - avatar_url: '', - name: 'User', - }, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ useTextGenerationCurrentProviderAndModelAndModelList: () => ({ @@ -243,6 +197,7 @@ vi.mock('@/service/client', async () => { function renderPreviewChat(props?: Partial>) { const store = createStore() + seedRegisteredConsoleStateFixture(store) const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -307,6 +262,7 @@ function RuntimeClearCommandHarness({ inputPlaceholder }: { inputPlaceholder: st function renderPreviewChatWithConversationHarness() { const store = createStore() + seedRegisteredConsoleStateFixture(store) const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -331,6 +287,7 @@ function renderPreviewChatWithConversationHarness() { function renderPreviewChatWithClearCommandHarness() { const store = createStore() + seedRegisteredConsoleStateFixture(store) const queryClient = new QueryClient({ defaultOptions: { queries: { diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/versions-panel.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/versions-panel.spec.tsx index 42b4f4c8067..90173d2bb80 100644 --- a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/versions-panel.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/versions-panel.spec.tsx @@ -1,5 +1,6 @@ import type { AgentConfigSnapshotSummaryResponse } from '@dify/contracts/api/console/agent/types.gen' -import { fireEvent, render, screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' +import { render } from '@/test/console/render' import { AgentPreviewVersionsPanel } from '../versions-panel' const versions: AgentConfigSnapshotSummaryResponse[] = [ @@ -58,10 +59,9 @@ vi.mock('@/service/client', () => ({ }, })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') + return createAccountStateModuleMock(() => ({ userProfile: { id: 'user-1', name: 'Alice', @@ -69,57 +69,6 @@ vi.mock('@/context/account-state', async (importOriginal) => { }, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: 'user-1', - name: 'Alice', - email: 'alice@example.com', - }, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: 'user-1', - name: 'Alice', - email: 'alice@example.com', - }, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: 'user-1', - name: 'Alice', - email: 'alice@example.com', - }, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - userProfile: { - id: 'user-1', - name: 'Alice', - email: 'alice@example.com', - }, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) describe('AgentPreviewVersionsPanel', () => { beforeEach(() => { diff --git a/web/features/deployments/create-guide/state/__tests__/index.spec.ts b/web/features/deployments/create-guide/state/__tests__/index.spec.ts index 1818482bebf..4aa7035d039 100644 --- a/web/features/deployments/create-guide/state/__tests__/index.spec.ts +++ b/web/features/deployments/create-guide/state/__tests__/index.spec.ts @@ -1,6 +1,5 @@ -import type { Getter } from 'jotai' -import { atom, createStore } from 'jotai' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { createQueryAtomTestStore } from '@/test/query-atom' import { dslFileAtom, effectiveMethodAtom, @@ -34,73 +33,23 @@ type InfiniteQueryOptions = QueryOptions & { input?: (pageParam: number) => unknown } -type QueryResult = { - data?: unknown - hasNextPage?: boolean - isError?: boolean - isFetching?: boolean - isFetchingNextPage?: boolean - isLoading?: boolean - isPlaceholderData?: boolean - isSuccess?: boolean -} - -const mockQueryResults = vi.hoisted(() => ({ - current: new Map(), -})) - -vi.mock('jotai-tanstack-query', () => ({ - atomWithInfiniteQuery: (createOptions: (get: Getter) => InfiniteQueryOptions) => - atom((get) => { - const options = createOptions(get) - const queryName = String(options.queryKey?.[0] ?? 'unknown') - const queryResult = mockQueryResults.current.get(queryName) - - return { - ...options, - data: { pages: [{ data: [] }] }, - hasNextPage: false, - isFetching: false, - isFetchingNextPage: false, - isLoading: false, - isPlaceholderData: false, - isSuccess: Boolean(queryResult?.data), - fetchNextPage: vi.fn(), - ...queryResult, - } - }), - atomWithMutation: () => - atom(() => ({ - isPending: false, - mutateAsync: vi.fn(), - })), - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => { - const options = createOptions(get) - const queryName = String(options.queryKey?.[0] ?? 'unknown') - const queryResult = - options.enabled === false ? undefined : mockQueryResults.current.get(queryName) - - return { - ...options, - data: undefined, - isError: false, - isFetching: false, - isLoading: false, - isSuccess: false, - ...queryResult, - } - }), +const queryOptionsMocks = vi.hoisted(() => ({ + sourceApps: vi.fn(), })) vi.mock('@/service/client', () => ({ consoleQuery: { apps: { get: { - infiniteOptions: (options: InfiniteQueryOptions) => ({ - ...options, - queryKey: ['sourceApps'], - }), + infiniteOptions: (options: InfiniteQueryOptions) => { + queryOptionsMocks.sourceApps(options) + + return { + ...options, + queryKey: ['sourceApps'], + queryFn: async () => ({ data: [], has_more: false, page: 1 }), + } + }, }, }, enterprise: { @@ -109,10 +58,12 @@ vi.mock('@/service/client', () => ({ infiniteOptions: (options: InfiniteQueryOptions) => ({ ...options, queryKey: ['existingInstanceNames'], + queryFn: async () => ({ appInstances: [], pagination: {} }), }), queryOptions: (options: QueryOptions) => ({ ...options, queryKey: ['instanceNameConflict'], + queryFn: async () => ({ appInstances: [] }), }), }, }, @@ -121,6 +72,7 @@ vi.mock('@/service/client', () => ({ queryOptions: (options: QueryOptions) => ({ ...options, queryKey: ['environments'], + queryFn: async () => ({ environments: [] }), }), }, }, @@ -129,12 +81,14 @@ vi.mock('@/service/client', () => ({ queryOptions: (options: QueryOptions) => ({ ...options, queryKey: ['deploymentOptions'], + queryFn: async () => ({ options: { credentialSlots: [], envVarSlots: [] } }), }), }, precheckRelease: { queryOptions: (options: QueryOptions) => ({ ...options, queryKey: ['precheckRelease'], + queryFn: async () => ({ canCreate: false, unsupportedNodes: [] }), }), }, }, @@ -149,11 +103,10 @@ function workflowDsl() { describe('create deployment guide state', () => { beforeEach(() => { vi.clearAllMocks() - mockQueryResults.current.clear() }) it('should keep the guide on source app mode when DSL import is disabled', () => { - const store = createStore() + const { store } = createQueryAtomTestStore() store.set(selectMethodAtom, 'importDsl') @@ -162,62 +115,54 @@ describe('create deployment guide state', () => { }) it('should keep source app loading enabled if stale state points to DSL import', () => { - const store = createStore() + const { store } = createQueryAtomTestStore() store.set(methodAtom, 'importDsl') - const sourceAppsQuery = store.get(sourceAppsQueryAtom) as unknown as { enabled?: boolean } + store.get(sourceAppsQueryAtom) expect(store.get(effectiveMethodAtom)).toBe('bindApp') - expect(sourceAppsQuery.enabled).toBe(true) + expect(queryOptionsMocks.sourceApps).toHaveBeenLastCalledWith( + expect.objectContaining({ enabled: true }), + ) }) it('should continue from source app mode and auto-fill unique release metadata', () => { - const store = createStore() - mockQueryResults.current.set('sourceApps', { - data: { - pages: [ - { - data: [ - { - id: 'source-app-1', - name: 'Customer Service', - mode: 'workflow', - }, - ], - }, - ], - }, - isSuccess: true, - }) - mockQueryResults.current.set('precheckRelease', { - data: { - canCreate: true, - unsupportedNodes: [], - }, - isSuccess: true, - }) - mockQueryResults.current.set('deploymentOptions', { - data: { - options: { - credentialSlots: [], - envVarSlots: [], + const { queryClient, store } = createQueryAtomTestStore() + queryClient.setQueryData(['sourceApps'], { + pages: [ + { + data: [ + { + id: 'source-app-1', + name: 'Customer Service', + mode: 'workflow', + }, + ], }, - }, - isSuccess: true, + ], + pageParams: [1], }) - mockQueryResults.current.set('existingInstanceNames', { - data: { - pages: [ - { - appInstances: [ - { displayName: 'Customer Service' }, - { displayName: 'Customer Service 1' }, - ], - }, - ], + queryClient.setQueryData(['precheckRelease'], { + canCreate: true, + unsupportedNodes: [], + }) + queryClient.setQueryData(['deploymentOptions'], { + options: { + credentialSlots: [], + envVarSlots: [], }, - isSuccess: true, + }) + queryClient.setQueryData(['existingInstanceNames'], { + pages: [ + { + appInstances: [ + { displayName: 'Customer Service' }, + { displayName: 'Customer Service 1' }, + ], + }, + ], + pageParams: [1], }) expect(store.get(sourceCanGoNextAtom)).toBe(true) @@ -237,16 +182,16 @@ describe('create deployment guide state', () => { }) it('should read selected DSL file content through the file content query', () => { - const store = createStore() + const { queryClient, store } = createQueryAtomTestStore() const text = vi.fn().mockResolvedValue(workflowDsl()) const file = new File([], 'workflow.yml', { type: 'text/yaml' }) Object.defineProperty(file, 'text', { value: text }) store.set(selectDslFileAtom, file) - mockQueryResults.current.set('createGuideDslFileContent', { - data: workflowDsl(), - isSuccess: true, - }) + queryClient.setQueryData( + ['createGuideDslFileContent', 1, file, file.name, file.size, file.lastModified], + workflowDsl(), + ) expect(text).not.toHaveBeenCalled() expect(store.get(dslFileAtom)).toBe(file) diff --git a/web/features/deployments/create-release/state/__tests__/dsl-enabled.spec.ts b/web/features/deployments/create-release/state/__tests__/dsl-enabled.spec.ts index baf2436452e..87b17b9848d 100644 --- a/web/features/deployments/create-release/state/__tests__/dsl-enabled.spec.ts +++ b/web/features/deployments/create-release/state/__tests__/dsl-enabled.spec.ts @@ -1,113 +1,44 @@ -import type { Getter } from 'jotai' -import { QueryClient, skipToken } from '@tanstack/react-query' -import { atom, createStore } from 'jotai' -import { queryClientAtom } from 'jotai-tanstack-query' +import type { QueryClient } from '@tanstack/react-query' +import { skipToken } from '@tanstack/react-query' import { beforeEach, describe, expect, it, vi } from 'vitest' - -type QueryResult = { - data?: unknown - isError?: boolean - isFetching?: boolean - isLoading?: boolean - isSuccess?: boolean -} +import { createQueryAtomTestStore } from '@/test/query-atom' type QueryOptions = { enabled?: boolean input?: unknown - queryFn?: () => unknown - queryKey?: readonly unknown[] - retry?: boolean } type InfiniteQueryOptions = QueryOptions & { input?: (pageParam: number) => unknown } -type MutationResult = { - isPending: boolean - mutateAsync: ReturnType -} - -const mockQueryResults = vi.hoisted(() => ({ - current: new Map(), -})) - -const mockQueryOptions = vi.hoisted(() => ({ - current: new Map(), -})) - -const mockCreateReleaseMutation = vi.hoisted<{ current: MutationResult }>(() => ({ - current: { - isPending: false, - mutateAsync: vi.fn(), - }, -})) +const mockSourceAppsInfiniteOptions = vi.hoisted(() => vi.fn()) +const mockPrecheckReleaseQueryOptions = vi.hoisted(() => vi.fn()) +const mockCreateRelease = vi.hoisted(() => vi.fn()) vi.mock('../../../shared/domain/feature-flags', () => ({ isDeploymentDslImportEnabled: true, })) -vi.mock('jotai-tanstack-query', async (importOriginal) => { - const actual = await importOriginal() - - return { - ...actual, - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => { - const options = createOptions(get) - const queryKey = Array.isArray(options.queryKey) ? options.queryKey[0] : undefined - const queryName = typeof queryKey === 'string' ? queryKey : 'unknown' - const queryResult = - options.enabled === false ? undefined : mockQueryResults.current.get(queryName) - - mockQueryOptions.current.set(queryName, options) - - return { - ...options, - data: undefined, - isError: false, - isFetching: false, - isLoading: false, - isSuccess: false, - ...queryResult, - } - }), - atomWithInfiniteQuery: (createOptions: (get: Getter) => InfiniteQueryOptions) => - atom((get) => { - const options = createOptions(get) - const queryKey = Array.isArray(options.queryKey) ? options.queryKey[0] : undefined - const queryName = typeof queryKey === 'string' ? queryKey : 'unknown' - const queryResult = - options.enabled === false ? undefined : mockQueryResults.current.get(queryName) - - mockQueryOptions.current.set(queryName, options) - - return { - ...options, - data: undefined, - hasNextPage: false, - isError: false, - isFetching: false, - isFetchingNextPage: false, - isLoading: false, - isPlaceholderData: false, - isSuccess: false, - ...queryResult, - } - }), - atomWithMutation: () => atom(() => mockCreateReleaseMutation.current), - } -}) - vi.mock('@/service/client', () => ({ consoleQuery: { apps: { get: { - infiniteOptions: (options: InfiniteQueryOptions) => ({ - ...options, - queryKey: ['sourceApps', options.input], - }), + infiniteOptions: (options: InfiniteQueryOptions) => { + mockSourceAppsInfiniteOptions(options) + + return { + ...options, + queryKey: ['sourceApps', options.input], + queryFn: async () => ({ + data: [], + has_more: false, + limit: 20, + page: 1, + total: 0, + }), + } + }, }, }, enterprise: { @@ -121,13 +52,20 @@ vi.mock('@/service/client', () => ({ input === undefined ? ['listReleases'] : ['listReleases', input], }, precheckRelease: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['precheckRelease', options.input], - }), + queryOptions: (options: QueryOptions) => { + mockPrecheckReleaseQueryOptions(options) + + return { + ...options, + queryKey: ['precheckRelease', options.input], + } + }, }, createRelease: { - mutationOptions: () => ({ mutationKey: ['createRelease'] }), + mutationOptions: () => ({ + mutationKey: ['createRelease'], + mutationFn: mockCreateRelease, + }), }, }, }, @@ -140,18 +78,11 @@ async function loadState() { async function mountedStore() { const state = await loadState() - const store = createStore() - const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }) - store.set(queryClientAtom, queryClient) + const { queryClient, store } = createQueryAtomTestStore() const unsubscribe = store.sub(state.createReleaseFormIsSubmittingAtom, () => undefined) return { + queryClient, state, store, unsubscribe, @@ -162,35 +93,33 @@ function workflowDsl() { return ['app:', ' mode: workflow', ' name: Release source'].join('\n') } -function setDslFileContentResult(overrides: QueryResult = {}) { - mockQueryResults.current.set('createReleaseDslFileContent', { - data: workflowDsl(), - isSuccess: true, - ...overrides, - }) +function setDslFileContentResult(queryClient: QueryClient, file: File, fileReadVersion: number) { + queryClient.setQueryData( + ['createReleaseDslFileContent', fileReadVersion, file, file.name, file.size, file.lastModified], + workflowDsl(), + ) } -function setPrecheckReleaseResult(overrides: QueryResult = {}) { - mockQueryResults.current.set('precheckRelease', { - data: { - gateCommitId: 'gate-commit-1', - canCreate: true, - unsupportedNodes: [], - }, - isSuccess: true, - ...overrides, +function setPrecheckReleaseResult( + queryClient: QueryClient, + input: { + body: { + appInstanceId: string + dsl: string + } + }, +) { + queryClient.setQueryData(['precheckRelease', input], { + gateCommitId: 'gate-commit-1', + canCreate: true, + unsupportedNodes: [], }) } describe('create release state with DSL import enabled', () => { beforeEach(() => { vi.clearAllMocks() - mockQueryResults.current.clear() - mockQueryOptions.current.clear() - mockCreateReleaseMutation.current = { - isPending: false, - mutateAsync: vi.fn(), - } + mockCreateRelease.mockReset() }) it('should enable source app search with the current search text while creating from an app', async () => { @@ -199,9 +128,8 @@ describe('create release state with DSL import enabled', () => { store.set(state.openCreateReleaseDialogAtom) store.set(state.createReleaseSourceAppSearchTextAtom, 'customer') - const sourceAppsQuery = store.get( - state.createReleaseSourceAppsQueryAtom, - ) as unknown as InfiniteQueryOptions + store.get(state.createReleaseSourceAppsQueryAtom) + const sourceAppsQuery = mockSourceAppsInfiniteOptions.mock.lastCall?.[0] as InfiniteQueryOptions expect(sourceAppsQuery.enabled).toBe(true) expect(sourceAppsQuery.input?.(2)).toEqual({ @@ -224,38 +152,45 @@ describe('create release state with DSL import enabled', () => { store.set(state.selectCreateReleaseSourceModeAtom, 'dsl') store.get(state.isCheckingCreateReleaseContentAtom) - expect(mockQueryOptions.current.get('precheckRelease')).toMatchObject({ - enabled: false, - input: skipToken, - }) + expect(mockPrecheckReleaseQueryOptions).toHaveBeenLastCalledWith( + expect.objectContaining({ + enabled: false, + input: skipToken, + }), + ) unsubscribe() }) it('should submit a DSL release with encoded workflow content', async () => { - const { state, store, unsubscribe } = await mountedStore() + const { queryClient, state, store, unsubscribe } = await mountedStore() const response = { release: { displayName: 'Release from DSL', }, } const file = new File([workflowDsl()], 'workflow.yml', { type: 'text/yaml' }) - mockCreateReleaseMutation.current.mutateAsync.mockResolvedValue(response) + mockCreateRelease.mockResolvedValue(response) store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1') store.set(state.openCreateReleaseDialogAtom) store.set(state.selectCreateReleaseSourceModeAtom, 'dsl') store.set(state.updateCreateReleaseDslFileAtom, file) - setDslFileContentResult() - setPrecheckReleaseResult() + setDslFileContentResult(queryClient, file, 2) + const encodedDslContent = store.get(state.createReleaseEncodedDslContentAtom) + setPrecheckReleaseResult(queryClient, { + body: { + appInstanceId: 'app-instance-1', + dsl: encodedDslContent, + }, + }) store.set(state.createReleaseNameFieldAtom, ' Release from DSL ') store.set(state.createReleaseDescriptionFieldAtom, ' Imported workflow ') const result = await store.set(state.submitCreateReleaseFormAtom) - const encodedDslContent = store.get(state.createReleaseEncodedDslContentAtom) expect(result).toBe(response) expect(encodedDslContent).not.toBe('') - expect(mockCreateReleaseMutation.current.mutateAsync).toHaveBeenCalledWith({ + expect(mockCreateRelease.mock.calls[0]?.[0]).toEqual({ body: { appInstanceId: 'app-instance-1', displayName: 'Release from DSL', diff --git a/web/features/deployments/create-release/state/__tests__/index.spec.ts b/web/features/deployments/create-release/state/__tests__/index.spec.ts index 090ca39af73..06af1bfa5ad 100644 --- a/web/features/deployments/create-release/state/__tests__/index.spec.ts +++ b/web/features/deployments/create-release/state/__tests__/index.spec.ts @@ -1,75 +1,17 @@ -import type { Getter } from 'jotai' +import type { QueryClient } from '@tanstack/react-query' import type { CreateReleaseFormValues } from '../index' -import { QueryClient, skipToken } from '@tanstack/react-query' -import { atom, createStore } from 'jotai' -import { queryClientAtom } from 'jotai-tanstack-query' +import { skipToken } from '@tanstack/react-query' import { beforeEach, describe, expect, it, vi } from 'vitest' import { consoleQuery } from '@/service/client' - -type QueryResult = { - data?: unknown - isError?: boolean - isFetching?: boolean - isLoading?: boolean - isSuccess?: boolean -} +import { createQueryAtomTestStore } from '@/test/query-atom' type QueryOptions = { enabled?: boolean input?: unknown - queryFn?: () => unknown - queryKey?: readonly unknown[] - retry?: boolean } -type MutationResult = { - isPending: boolean - mutateAsync: ReturnType -} - -const mockQueryResults = vi.hoisted(() => ({ - current: new Map(), -})) - -const mockQueryOptions = vi.hoisted(() => ({ - current: new Map(), -})) - -const mockCreateReleaseMutation = vi.hoisted<{ current: MutationResult }>(() => ({ - current: { - isPending: false, - mutateAsync: vi.fn(), - }, -})) - -vi.mock('jotai-tanstack-query', async (importOriginal) => { - const actual = await importOriginal() - - return { - ...actual, - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => { - const options = createOptions(get) - const queryKey = Array.isArray(options.queryKey) ? options.queryKey[0] : undefined - const queryName = typeof queryKey === 'string' ? queryKey : 'unknown' - const queryResult = - options.enabled === false ? undefined : mockQueryResults.current.get(queryName) - - mockQueryOptions.current.set(queryName, options) - - return { - ...options, - data: undefined, - isError: false, - isFetching: false, - isLoading: false, - isSuccess: false, - ...queryResult, - } - }), - atomWithMutation: () => atom(() => mockCreateReleaseMutation.current), - } -}) +const mockPrecheckReleaseQueryOptions = vi.hoisted(() => vi.fn()) +const mockCreateRelease = vi.hoisted(() => vi.fn()) vi.mock('@/service/client', () => ({ consoleQuery: { @@ -105,14 +47,21 @@ vi.mock('@/service/client', () => ({ }), }, precheckRelease: { - queryOptions: ({ enabled, input }: QueryOptions) => ({ - enabled, - input, - queryKey: ['precheckRelease', input], - }), + queryOptions: ({ enabled, input }: QueryOptions) => { + mockPrecheckReleaseQueryOptions({ enabled, input }) + + return { + enabled, + input, + queryKey: ['precheckRelease', input], + } + }, }, createRelease: { - mutationOptions: () => ({ mutationKey: ['createRelease'] }), + mutationOptions: () => ({ + mutationKey: ['createRelease'], + mutationFn: mockCreateRelease, + }), }, }, }, @@ -125,15 +74,7 @@ async function loadState() { async function mountedStore() { const state = await loadState() - const store = createStore() - const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }) - store.set(queryClientAtom, queryClient) + const { queryClient, store } = createQueryAtomTestStore() const unsubscribe = store.sub(state.createReleaseFormIsSubmittingAtom, () => undefined) return { @@ -170,39 +111,56 @@ function workflowDsl() { } function setDefaultSourceApp( + queryClient: QueryClient, + appInstanceId: string, defaultSourceApp = sourceApp({ id: 'default-source-app', name: 'Default Source App' }), ) { - mockQueryResults.current.set('listReleases', { - data: { + queryClient.setQueryData( + [ + 'listReleases', + { + params: { appInstanceId }, + query: { + pageNumber: 1, + resultsPerPage: 1, + }, + }, + ], + { releases: [ { + displayName: 'Previous Release', sourceAppId: defaultSourceApp.id, }, ], }, - isSuccess: true, - }) - mockQueryResults.current.set('appById', { - data: defaultSourceApp, - isSuccess: true, - }) + ) + queryClient.setQueryData( + ['appById', { params: { app_id: defaultSourceApp.id } }], + defaultSourceApp, + ) } function setPrecheckReleaseResult( + queryClient: QueryClient, + input: { + body: { + appInstanceId: string + dsl?: string + sourceAppId?: string + } + }, overrides: { canCreate?: boolean matchedRelease?: unknown unsupportedNodes?: Array<{ id?: string; type?: string }> } = {}, ) { - mockQueryResults.current.set('precheckRelease', { - data: { - gateCommitId: 'gate-commit-1', - canCreate: true, - unsupportedNodes: [], - ...overrides, - }, - isSuccess: true, + queryClient.setQueryData(['precheckRelease', input], { + gateCommitId: 'gate-commit-1', + canCreate: true, + unsupportedNodes: [], + ...overrides, }) } @@ -227,23 +185,17 @@ function setCachedReleaseSummaries( ) } -function setDslFileContentResult(overrides: QueryResult = {}) { - mockQueryResults.current.set('createReleaseDslFileContent', { - data: workflowDsl(), - isSuccess: true, - ...overrides, - }) +function setDslFileContentResult(queryClient: QueryClient, file: File, fileReadVersion: number) { + queryClient.setQueryData( + ['createReleaseDslFileContent', fileReadVersion, file, file.name, file.size, file.lastModified], + workflowDsl(), + ) } describe('create release state', () => { beforeEach(() => { vi.clearAllMocks() - mockQueryResults.current.clear() - mockQueryOptions.current.clear() - mockCreateReleaseMutation.current = { - isPending: false, - mutateAsync: vi.fn(), - } + mockCreateRelease.mockReset() }) it('should validate release name only when submitting', async () => { @@ -251,7 +203,7 @@ describe('create release state', () => { await store.set(state.submitCreateReleaseFormAtom) - expect(mockCreateReleaseMutation.current.mutateAsync).not.toHaveBeenCalled() + expect(mockCreateRelease).not.toHaveBeenCalled() expect( hasValidationIssue( store.get(state.createReleaseNameFieldAtom).meta?.errors ?? [], @@ -263,17 +215,22 @@ describe('create release state', () => { }) it('should submit after fixing release name following a submit validation error', async () => { - const { state, store, unsubscribe } = await mountedStore() + const { queryClient, state, store, unsubscribe } = await mountedStore() const response = { release: { displayName: 'Release 1', }, } - mockCreateReleaseMutation.current.mutateAsync.mockResolvedValue(response) + mockCreateRelease.mockResolvedValue(response) store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1') store.set(state.openCreateReleaseDialogAtom) - setDefaultSourceApp() - setPrecheckReleaseResult() + setDefaultSourceApp(queryClient, 'app-instance-1') + setPrecheckReleaseResult(queryClient, { + body: { + appInstanceId: 'app-instance-1', + sourceAppId: 'default-source-app', + }, + }) await store.set(state.submitCreateReleaseFormAtom) store.set(state.createReleaseNameFieldAtom, 'Release 1') @@ -281,7 +238,7 @@ describe('create release state', () => { const result = await store.set(state.submitCreateReleaseFormAtom) expect(result).toBe(response) - expect(mockCreateReleaseMutation.current.mutateAsync).toHaveBeenCalledTimes(1) + expect(mockCreateRelease).toHaveBeenCalledTimes(1) unsubscribe() }) @@ -298,10 +255,10 @@ describe('create release state', () => { }) it('should derive default source app selection from the latest release source', async () => { - const { state, store, unsubscribe } = await mountedStore() + const { queryClient, state, store, unsubscribe } = await mountedStore() store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1') store.set(state.openCreateReleaseDialogAtom) - setDefaultSourceApp() + setDefaultSourceApp(queryClient, 'app-instance-1') expect(store.get(state.createReleaseSelectedSourceAppAtom)).toEqual({ id: 'default-source-app', @@ -314,11 +271,11 @@ describe('create release state', () => { }) it('should derive workflow DSL read state when selecting a DSL file', async () => { - const { state, store, unsubscribe } = await mountedStore() + const { queryClient, state, store, unsubscribe } = await mountedStore() const file = new File([workflowDsl()], 'workflow.yml', { type: 'text/yaml' }) store.set(state.updateCreateReleaseDslFileAtom, file) - setDslFileContentResult() + setDslFileContentResult(queryClient, file, 1) expect(store.get(state.createReleaseDslFileFieldAtom).value).toBe(file) expect(store.get(state.createReleaseDslContentAtom)).toBe(workflowDsl()) @@ -331,11 +288,11 @@ describe('create release state', () => { }) it('should reset DSL state when switching back to source app mode', async () => { - const { state, store, unsubscribe } = await mountedStore() + const { queryClient, state, store, unsubscribe } = await mountedStore() const file = new File([workflowDsl()], 'workflow.yml', { type: 'text/yaml' }) store.set(state.updateCreateReleaseDslFileAtom, file) - setDslFileContentResult() + setDslFileContentResult(queryClient, file, 1) store.set(state.selectCreateReleaseSourceModeAtom, 'sourceApp') expect(store.get(state.createReleaseSourceModeFieldAtom).value).toBe('sourceApp') @@ -373,10 +330,12 @@ describe('create release state', () => { store.set(state.openCreateReleaseDialogAtom) store.get(state.isCheckingCreateReleaseContentAtom) - expect(mockQueryOptions.current.get('precheckRelease')).toMatchObject({ - enabled: false, - input: skipToken, - }) + expect(mockPrecheckReleaseQueryOptions).toHaveBeenLastCalledWith( + expect.objectContaining({ + enabled: false, + input: skipToken, + }), + ) unsubscribe() }) @@ -384,15 +343,14 @@ describe('create release state', () => { it('should capture DSL file read failures and clear them when opening or closing the dialog', async () => { const { state, store, unsubscribe } = await mountedStore() const file = new File(['broken'], 'broken.yml', { type: 'text/yaml' }) + vi.spyOn(file, 'text').mockRejectedValue(new Error('read failed')) store.set(state.updateCreateReleaseDslFileAtom, file) - setDslFileContentResult({ - data: undefined, - isError: true, - isSuccess: false, - }) + const unsubscribeDslReadError = store.sub(state.createReleaseDslReadErrorAtom, () => undefined) - expect(store.get(state.createReleaseDslReadErrorAtom)).toBe(true) + await vi.waitFor(() => { + expect(store.get(state.createReleaseDslReadErrorAtom)).toBe(true) + }) store.set(state.openCreateReleaseDialogAtom) expect(store.get(state.createReleaseDialogOpenAtom)).toBe(true) @@ -401,15 +359,21 @@ describe('create release state', () => { store.set(state.closeCreateReleaseDialogAtom) expect(store.get(state.createReleaseDialogOpenAtom)).toBe(false) + unsubscribeDslReadError() unsubscribe() }) it('should derive content readiness from release content precheck', async () => { - const { state, store, unsubscribe } = await mountedStore() + const { queryClient, state, store, unsubscribe } = await mountedStore() store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1') store.set(state.openCreateReleaseDialogAtom) - setDefaultSourceApp() - setPrecheckReleaseResult() + setDefaultSourceApp(queryClient, 'app-instance-1') + setPrecheckReleaseResult(queryClient, { + body: { + appInstanceId: 'app-instance-1', + sourceAppId: 'default-source-app', + }, + }) expect(store.get(state.createReleaseContentReadyAtom)).toBe(true) @@ -444,14 +408,23 @@ describe('create release state', () => { }) it('should expose unsupported nodes from release content precheck', async () => { - const { state, store, unsubscribe } = await mountedStore() + const { queryClient, state, store, unsubscribe } = await mountedStore() store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1') store.set(state.openCreateReleaseDialogAtom) - setDefaultSourceApp() - setPrecheckReleaseResult({ - canCreate: false, - unsupportedNodes: [{ id: 'precheck-node' }], - }) + setDefaultSourceApp(queryClient, 'app-instance-1') + setPrecheckReleaseResult( + queryClient, + { + body: { + appInstanceId: 'app-instance-1', + sourceAppId: 'default-source-app', + }, + }, + { + canCreate: false, + unsupportedNodes: [{ id: 'precheck-node' }], + }, + ) expect(store.get(state.createReleaseUnsupportedDslNodesAtom)).toEqual([{ id: 'precheck-node' }]) @@ -459,24 +432,29 @@ describe('create release state', () => { }) it('should submit source app release with the checked source and metadata', async () => { - const { state, store, unsubscribe } = await mountedStore() + const { queryClient, state, store, unsubscribe } = await mountedStore() const response = { release: { displayName: 'Release 1', }, } - mockCreateReleaseMutation.current.mutateAsync.mockResolvedValue(response) + mockCreateRelease.mockResolvedValue(response) store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1') store.set(state.openCreateReleaseDialogAtom) - setDefaultSourceApp() - setPrecheckReleaseResult() + setDefaultSourceApp(queryClient, 'app-instance-1') + setPrecheckReleaseResult(queryClient, { + body: { + appInstanceId: 'app-instance-1', + sourceAppId: 'default-source-app', + }, + }) store.set(state.createReleaseNameFieldAtom, ' Release 1 ') store.set(state.createReleaseDescriptionFieldAtom, ' Initial rollout ') const result = await store.set(state.submitCreateReleaseFormAtom) expect(result).toBe(response) - expect(mockCreateReleaseMutation.current.mutateAsync).toHaveBeenCalledWith({ + expect(mockCreateRelease.mock.calls[0]?.[0]).toEqual({ body: { appInstanceId: 'app-instance-1', sourceAppId: 'default-source-app', @@ -493,27 +471,37 @@ describe('create release state', () => { const { queryClient, state, store, unsubscribe } = await mountedStore() store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1') store.set(state.openCreateReleaseDialogAtom) - setDefaultSourceApp() - setPrecheckReleaseResult() + setDefaultSourceApp(queryClient, 'app-instance-1') + setPrecheckReleaseResult(queryClient, { + body: { + appInstanceId: 'app-instance-1', + sourceAppId: 'default-source-app', + }, + }) setCachedReleaseSummaries(queryClient, 'app-instance-1', ['Release 1']) store.set(state.createReleaseNameFieldAtom, 'Release 1') const result = await store.set(state.submitCreateReleaseFormAtom) expect(result).toBeUndefined() - expect(mockCreateReleaseMutation.current.mutateAsync).not.toHaveBeenCalled() + expect(mockCreateRelease).not.toHaveBeenCalled() unsubscribe() }) it('should propagate create release submission errors', async () => { - const { state, store, unsubscribe } = await mountedStore() + const { queryClient, state, store, unsubscribe } = await mountedStore() const submitError = new Error('submit failed') - mockCreateReleaseMutation.current.mutateAsync.mockRejectedValue(submitError) + mockCreateRelease.mockRejectedValue(submitError) store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1') store.set(state.openCreateReleaseDialogAtom) - setDefaultSourceApp() - setPrecheckReleaseResult() + setDefaultSourceApp(queryClient, 'app-instance-1') + setPrecheckReleaseResult(queryClient, { + body: { + appInstanceId: 'app-instance-1', + sourceAppId: 'default-source-app', + }, + }) store.set(state.createReleaseNameFieldAtom, 'Release 1') await expect(store.set(state.submitCreateReleaseFormAtom)).rejects.toThrow(submitError) diff --git a/web/features/deployments/deploy-drawer/state/__tests__/index.spec.ts b/web/features/deployments/deploy-drawer/state/__tests__/index.spec.ts index b6bc75e00dc..93ebb7daaa1 100644 --- a/web/features/deployments/deploy-drawer/state/__tests__/index.spec.ts +++ b/web/features/deployments/deploy-drawer/state/__tests__/index.spec.ts @@ -6,7 +6,7 @@ import type { EnvVarSlot, Release, } from '@dify/contracts/enterprise/types.gen' -import type { Getter } from 'jotai' +import type { QueryClient } from '@tanstack/react-query' import { EnvVarValueSource, EnvVarValueType, @@ -14,118 +14,54 @@ import { RuntimeInstanceStatus, } from '@dify/contracts/enterprise/types.gen' import { skipToken } from '@tanstack/react-query' -import { atom, createStore } from 'jotai' import { beforeEach, describe, expect, it, vi } from 'vitest' - -type QueryOptions = { - data?: unknown - enabled?: boolean - input?: unknown - isError?: boolean - isFetching?: boolean - isLoading?: boolean - queryKey?: readonly unknown[] - retry?: boolean -} - -type QueryResult = { - data?: { - options: DeploymentOptions - } - isLoading: boolean - isFetching: boolean - isError: boolean -} +import { createQueryAtomTestStore } from '@/test/query-atom' type DeploymentOptions = { credentialSlots: CredentialSlot[] envVarSlots: EnvVarSlot[] } -type MutationOptions = { - mutationKey?: readonly string[] -} - -type MutationResult = { - isPending: boolean - mutate: ReturnType -} - -const mockDeploymentOptionsQuery = vi.hoisted<{ current: QueryResult }>(() => ({ - current: { - isLoading: false, - isFetching: false, - isError: false, - }, -})) -const mockPromoteMutate = vi.hoisted(() => vi.fn()) -const mockRollbackMutate = vi.hoisted(() => vi.fn()) -const mockPromoteMutation = vi.hoisted<{ current: MutationResult }>(() => ({ - current: { - isPending: false, - mutate: mockPromoteMutate, - }, -})) -const mockRollbackMutation = vi.hoisted<{ current: MutationResult }>(() => ({ - current: { - isPending: false, - mutate: mockRollbackMutate, - }, -})) - -vi.mock('jotai-tanstack-query', () => ({ - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => { - const options = createOptions(get) - if (options.queryKey?.[0] === 'computeDeploymentOptions') { - return { - ...options, - ...mockDeploymentOptionsQuery.current, - } - } - - return { - ...options, - data: undefined, - isLoading: false, - isFetching: false, - isError: false, - } - }), - atomWithMutation: (createOptions: () => MutationOptions) => - atom(() => { - const options = createOptions() - return options.mutationKey?.[0] === 'rollback' - ? mockRollbackMutation.current - : mockPromoteMutation.current - }), -})) +const mockReleaseDeploymentViewQueryOptions = vi.hoisted(() => vi.fn()) +const mockPromoteMutation = vi.hoisted(() => vi.fn()) +const mockRollbackMutation = vi.hoisted(() => vi.fn()) vi.mock('@/service/client', () => ({ consoleQuery: { enterprise: { releaseService: { computeReleaseDeploymentView: { - queryOptions: ({ enabled, input }: { enabled: boolean; input: unknown }) => ({ - enabled, - input, - queryKey: ['computeReleaseDeploymentView', input], - }), + queryOptions: ({ enabled, input }: { enabled: boolean; input: unknown }) => { + mockReleaseDeploymentViewQueryOptions({ enabled, input }) + return { + enabled, + input, + queryKey: ['computeReleaseDeploymentView', input], + queryFn: async () => undefined, + } + }, }, computeDeploymentOptions: { queryOptions: ({ enabled, input }: { enabled: boolean; input: unknown }) => ({ enabled, input, queryKey: ['computeDeploymentOptions', input], + queryFn: async () => undefined, }), }, }, deploymentService: { promote: { - mutationOptions: () => ({ mutationKey: ['promote'] }), + mutationOptions: () => ({ + mutationKey: ['promote'], + mutationFn: mockPromoteMutation, + }), }, rollback: { - mutationOptions: () => ({ mutationKey: ['rollback'] }), + mutationOptions: () => ({ + mutationKey: ['rollback'], + mutationFn: mockRollbackMutation, + }), }, }, }, @@ -238,38 +174,36 @@ function deployConfig() { } } -function setQueryOptions(options: DeploymentOptions) { - mockDeploymentOptionsQuery.current = { - data: { +function setQueryOptions( + queryClient: QueryClient, + options: DeploymentOptions, + releaseId = 'release-2', + environmentId = 'environment-1', +) { + queryClient.setQueryData( + [ + 'computeDeploymentOptions', + { + body: { + releaseId, + environmentId, + }, + }, + ], + { options, }, - isLoading: false, - isFetching: false, - isError: false, - } + ) } describe('deploy drawer state', () => { beforeEach(() => { vi.clearAllMocks() - mockDeploymentOptionsQuery.current = { - isLoading: false, - isFetching: false, - isError: false, - } - mockPromoteMutation.current = { - isPending: false, - mutate: mockPromoteMutate, - } - mockRollbackMutation.current = { - isPending: false, - mutate: mockRollbackMutate, - } }) it('should open and close the deploy drawer with route identity', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() store.set(state.openDeployDrawerAtom, { appInstanceId: 'app-instance-1', @@ -292,16 +226,18 @@ describe('deploy drawer state', () => { it('should disable release deployment view query with skipToken until form app instance exists', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() - expect(store.get(state.releaseDeploymentViewQueryAtom)).toMatchObject({ + store.get(state.releaseDeploymentViewQueryAtom) + expect(mockReleaseDeploymentViewQueryOptions).toHaveBeenLastCalledWith({ enabled: false, input: skipToken, }) store.set(state.deployFormAppInstanceIdAtom, 'app-instance-1') - expect(store.get(state.releaseDeploymentViewQueryAtom)).toMatchObject({ + store.get(state.releaseDeploymentViewQueryAtom) + expect(mockReleaseDeploymentViewQueryOptions).toHaveBeenLastCalledWith({ enabled: true, input: { params: { appInstanceId: 'app-instance-1' } }, }) @@ -309,7 +245,7 @@ describe('deploy drawer state', () => { it('should derive default environment and release selections from config', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() store.set(state.deployReadyFormConfigAtom, { ...deployConfig(), lockedEnvId: 'environment-2', @@ -331,10 +267,10 @@ describe('deploy drawer state', () => { it('should reset bindings, env vars, and validation state when target changes', async () => { const state = await loadState() - const store = createStore() + const { queryClient, store } = createQueryAtomTestStore() const slot = credentialSlot() store.set(state.deployReadyFormConfigAtom, deployConfig()) - setQueryOptions({ + setQueryOptions(queryClient, { credentialSlots: [slot], envVarSlots: [envVarSlot()], }) @@ -374,12 +310,12 @@ describe('deploy drawer state', () => { it('should derive binding and env var readiness from deployment options', async () => { const state = await loadState() - const store = createStore() + const { queryClient, store } = createQueryAtomTestStore() const slot = credentialSlot({ lastCredentialId: 'credential-2', }) store.set(state.deployReadyFormConfigAtom, deployConfig()) - setQueryOptions({ + setQueryOptions(queryClient, { credentialSlots: [slot], envVarSlots: [ envVarSlot({ @@ -408,9 +344,9 @@ describe('deploy drawer state', () => { it('should block submit until required literal env vars are valid', async () => { const state = await loadState() - const store = createStore() + const { queryClient, store } = createQueryAtomTestStore() store.set(state.deployReadyFormConfigAtom, deployConfig()) - setQueryOptions({ + setQueryOptions(queryClient, { credentialSlots: [ credentialSlot({ candidates: [credentialCandidate({ credentialId: 'credential-1' })], @@ -442,14 +378,9 @@ describe('deploy drawer state', () => { it('should submit a promote deployment with selected credentials and env vars', async () => { const state = await loadState() - const store = createStore() - mockPromoteMutate.mockImplementation( - (_variables: unknown, options?: { onSuccess?: () => void }) => { - options?.onSuccess?.() - }, - ) + const { queryClient, store } = createQueryAtomTestStore() store.set(state.deployReadyFormConfigAtom, deployConfig()) - setQueryOptions({ + setQueryOptions(queryClient, { credentialSlots: [ credentialSlot({ candidates: [credentialCandidate({ credentialId: 'credential-1' })], @@ -471,38 +402,37 @@ describe('deploy drawer state', () => { deployFailedMessage: 'Deploy failed', }) - expect(mockPromoteMutate).toHaveBeenCalledWith( - { - params: { - appInstanceId: 'app-instance-1', - environmentId: 'environment-1', + await vi.waitFor(() => { + expect(mockPromoteMutation).toHaveBeenCalledWith( + { + params: { + appInstanceId: 'app-instance-1', + environmentId: 'environment-1', + }, + body: { + appInstanceId: 'app-instance-1', + environmentId: 'environment-1', + releaseId: 'release-2', + credentials: [ + { + providerId: 'langgenius/openai', + category: PluginCategory.PLUGIN_CATEGORY_MODEL, + credentialId: 'credential-1', + }, + ], + envVars: [ + { + key: 'API_KEY', + value: 'secret', + valueSource: EnvVarValueSource.ENV_VAR_VALUE_SOURCE_LITERAL, + }, + ], + idempotencyKey: expect.any(String), + }, }, - body: { - appInstanceId: 'app-instance-1', - environmentId: 'environment-1', - releaseId: 'release-2', - credentials: [ - { - providerId: 'langgenius/openai', - category: PluginCategory.PLUGIN_CATEGORY_MODEL, - credentialId: 'credential-1', - }, - ], - envVars: [ - { - key: 'API_KEY', - value: 'secret', - valueSource: EnvVarValueSource.ENV_VAR_VALUE_SOURCE_LITERAL, - }, - ], - idempotencyKey: expect.any(String), - }, - }, - expect.objectContaining({ - onSuccess: expect.any(Function), - onError: expect.any(Function), - }), - ) + expect.any(Object), + ) + }) expect(store.get(state.deployDrawerOpenAtom)).toBe(false) }) }) diff --git a/web/features/deployments/detail/__tests__/state.spec.ts b/web/features/deployments/detail/__tests__/state.spec.ts index 92ab6c4a91f..2939619d00c 100644 --- a/web/features/deployments/detail/__tests__/state.spec.ts +++ b/web/features/deployments/detail/__tests__/state.spec.ts @@ -1,58 +1,45 @@ import type { EnvironmentDeployment } from '@dify/contracts/enterprise/types.gen' -import type { Getter } from 'jotai' import { RuntimeInstanceStatus } from '@dify/contracts/enterprise/types.gen' import { skipToken } from '@tanstack/react-query' -import { atom, createStore } from 'jotai' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { setNextRouteStateAtom } from '@/app/components/next-route-state/atoms' +import { createQueryAtomTestStore } from '@/test/query-atom' type QueryOptions = { - data?: unknown enabled?: boolean input?: unknown queryKey?: readonly unknown[] refetchInterval?: (query: { state: { data?: unknown } }) => number | false } -const mockEnvironmentDeploymentsData = vi.hoisted<{ - current?: { environmentDeployments: EnvironmentDeployment[] } -}>(() => ({})) - -vi.mock('jotai-tanstack-query', () => ({ - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => { - const options = createOptions(get) - return { - ...options, - data: - options.queryKey?.[0] === 'listEnvironmentDeployments' - ? mockEnvironmentDeploymentsData.current - : undefined, - isError: false, - isFetching: false, - isLoading: false, - isSuccess: false, - } - }), -})) +const mockGetAppInstanceQueryOptions = vi.hoisted(() => vi.fn()) +const mockListEnvironmentDeploymentsQueryOptions = vi.hoisted(() => vi.fn()) vi.mock('@/service/client', () => ({ consoleQuery: { enterprise: { appInstanceService: { getAppInstance: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['getAppInstance', options.input], - }), + queryOptions: (options: QueryOptions) => { + mockGetAppInstanceQueryOptions(options) + return { + ...options, + queryKey: ['getAppInstance', options.input], + queryFn: async () => undefined, + } + }, }, }, deploymentService: { listEnvironmentDeployments: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['listEnvironmentDeployments', options.input], - }), + queryOptions: (options: QueryOptions) => { + mockListEnvironmentDeploymentsQueryOptions(options) + return { + ...options, + queryKey: ['listEnvironmentDeployments', options.input], + queryFn: async () => undefined, + } + }, }, }, }, @@ -64,7 +51,7 @@ async function loadState() { } function setDeploymentRoute( - store: ReturnType, + store: ReturnType['store'], appInstanceId = 'app-instance-1', tab = 'overview', ) { @@ -94,47 +81,53 @@ function deploymentRow(id: string, overrides: Partial = { describe('deployment detail state', () => { beforeEach(() => { - mockEnvironmentDeploymentsData.current = undefined + mockGetAppInstanceQueryOptions.mockClear() + mockListEnvironmentDeploymentsQueryOptions.mockClear() }) it('should disable detail queries with skipToken until a route app instance exists', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() - expect(store.get(state.deploymentDetailAppInstanceQueryAtom)).toMatchObject({ - enabled: false, - input: skipToken, - }) - expect(store.get(state.deploymentEnvironmentDeploymentsQueryAtom)).toMatchObject({ + store.get(state.deploymentDetailAppInstanceQueryAtom) + expect(mockGetAppInstanceQueryOptions).toHaveBeenLastCalledWith({ enabled: false, input: skipToken, }) + store.get(state.deploymentEnvironmentDeploymentsQueryAtom) + expect(mockListEnvironmentDeploymentsQueryOptions).toHaveBeenLastCalledWith( + expect.objectContaining({ + enabled: false, + input: skipToken, + }), + ) }) it('should build detail query inputs from route identity', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() setDeploymentRoute(store) - expect(store.get(state.deploymentDetailAppInstanceQueryAtom)).toMatchObject({ + store.get(state.deploymentDetailAppInstanceQueryAtom) + expect(mockGetAppInstanceQueryOptions).toHaveBeenLastCalledWith({ enabled: true, input: { params: { appInstanceId: 'app-instance-1' } }, }) - const environmentDeploymentsQuery = store.get( - state.deploymentEnvironmentDeploymentsQueryAtom, - ) as unknown as QueryOptions - expect(environmentDeploymentsQuery).toMatchObject({ - enabled: true, - input: { params: { appInstanceId: 'app-instance-1' } }, - }) - expect(environmentDeploymentsQuery.refetchInterval).toEqual(expect.any(Function)) + store.get(state.deploymentEnvironmentDeploymentsQueryAtom) + expect(mockListEnvironmentDeploymentsQueryOptions).toHaveBeenLastCalledWith( + expect.objectContaining({ + enabled: true, + input: { params: { appInstanceId: 'app-instance-1' } }, + refetchInterval: expect.any(Function), + }), + ) }) it('should derive active detail tab from route pathname', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() setDeploymentRoute(store, 'app-instance-1', 'releases') expect(store.get(state.deploymentDetailActiveTabAtom)).toBe('releases') @@ -145,18 +138,22 @@ describe('deployment detail state', () => { it('should derive runtime instance rows from environment deployments', async () => { const state = await loadState() - const store = createStore() - mockEnvironmentDeploymentsData.current = { - environmentDeployments: [ - deploymentRow('running'), - deploymentRow('undeployed', { - status: RuntimeInstanceStatus.RUNTIME_INSTANCE_STATUS_UNSPECIFIED, - currentRelease: undefined, - desiredRelease: undefined, - currentDeployment: undefined, - }), - ], - } + const { queryClient, store } = createQueryAtomTestStore() + setDeploymentRoute(store) + queryClient.setQueryData( + ['listEnvironmentDeployments', { params: { appInstanceId: 'app-instance-1' } }], + { + environmentDeployments: [ + deploymentRow('running'), + deploymentRow('undeployed', { + status: RuntimeInstanceStatus.RUNTIME_INSTANCE_STATUS_UNSPECIFIED, + currentRelease: undefined, + desiredRelease: undefined, + currentDeployment: undefined, + }), + ], + }, + ) expect( store.get(state.deploymentRuntimeInstanceRowsAtom).map((row) => row.environment.id), diff --git a/web/features/deployments/detail/access/__tests__/state.spec.ts b/web/features/deployments/detail/access/__tests__/state.spec.ts index 96243487e25..ed7f76cc78c 100644 --- a/web/features/deployments/detail/access/__tests__/state.spec.ts +++ b/web/features/deployments/detail/access/__tests__/state.spec.ts @@ -1,8 +1,7 @@ -import type { Getter } from 'jotai' import { skipToken } from '@tanstack/react-query' -import { atom, createStore } from 'jotai' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { setNextRouteStateAtom } from '@/app/components/next-route-state/atoms' +import { createQueryAtomTestStore } from '@/test/query-atom' type QueryOptions = { enabled?: boolean @@ -10,27 +9,21 @@ type QueryOptions = { queryKey?: readonly unknown[] } -vi.mock('jotai-tanstack-query', () => ({ - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => ({ - ...createOptions(get), - data: undefined, - isError: false, - isFetching: false, - isLoading: false, - isSuccess: false, - })), -})) +const mockAccessSettingsQueryOptions = vi.hoisted(() => vi.fn()) vi.mock('@/service/client', () => ({ consoleQuery: { enterprise: { accessService: { getAccessSettings: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['getAccessSettings', options.input], - }), + queryOptions: (options: QueryOptions) => { + mockAccessSettingsQueryOptions(options) + return { + ...options, + queryKey: ['getAccessSettings', options.input], + queryFn: async () => undefined, + } + }, }, }, }, @@ -42,7 +35,7 @@ async function loadState() { } function setDeploymentRoute( - store: ReturnType, + store: ReturnType['store'], appInstanceId = 'app-instance-1', ) { store.set(setNextRouteStateAtom, { @@ -52,18 +45,24 @@ function setDeploymentRoute( } describe('deployment access state', () => { + beforeEach(() => { + mockAccessSettingsQueryOptions.mockClear() + }) + it('should gate access queries until a route app instance exists', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() - expect(store.get(state.accessSettingsQueryAtom)).toMatchObject({ + store.get(state.accessSettingsQueryAtom) + expect(mockAccessSettingsQueryOptions).toHaveBeenLastCalledWith({ enabled: false, input: skipToken, }) setDeploymentRoute(store) + store.get(state.accessSettingsQueryAtom) - expect(store.get(state.accessSettingsQueryAtom)).toMatchObject({ + expect(mockAccessSettingsQueryOptions).toHaveBeenLastCalledWith({ enabled: true, input: { params: { appInstanceId: 'app-instance-1' } }, }) diff --git a/web/features/deployments/detail/access/permissions/__tests__/permissions.spec.tsx b/web/features/deployments/detail/access/permissions/__tests__/permissions.spec.tsx index 33a1937b439..a14be0ea474 100644 --- a/web/features/deployments/detail/access/permissions/__tests__/permissions.spec.tsx +++ b/web/features/deployments/detail/access/permissions/__tests__/permissions.spec.tsx @@ -3,12 +3,13 @@ import type { Environment, EnvironmentAccessPolicy, } from '@dify/contracts/enterprise/types.gen' +import type { Atom, WritableAtom } from 'jotai' import type { ReactNode } from 'react' import { AccessMode, AccessSubjectType } from '@dify/contracts/enterprise/types.gen' import { fireEvent, render, screen } from '@testing-library/react' import { createStore, Provider as JotaiProvider } from 'jotai' import { describe, expect, it, vi } from 'vitest' -import { deploymentRouteAppInstanceIdAtom } from '../../../../route-state' +import { setNextRouteStateAtom } from '@/app/components/next-route-state/atoms' import { accessSettingsAtom, accessSettingsIsErrorAtom, @@ -18,13 +19,14 @@ import { EnvironmentPermissionRow } from '../environment-permission-row' import { AccessPermissionsSection } from '../section' const mockMutate = vi.hoisted(() => vi.fn()) -const mockUseAtomValue = vi.hoisted(() => vi.fn()) -vi.mock('jotai', async (importOriginal) => { - const actual = await importOriginal() +vi.mock('../../state', async () => { + const { atom } = await import('jotai') + return { - ...actual, - useAtomValue: mockUseAtomValue, + accessSettingsAtom: atom(undefined), + accessSettingsIsLoadingAtom: atom(false), + accessSettingsIsErrorAtom: atom(false), } }) @@ -58,8 +60,30 @@ vi.mock('@/service/client', () => ({ }, })) -function renderWithAtomStore(children: ReactNode) { - return render({children}) +function setTestAtom( + store: ReturnType, + target: Atom, + value: Value, +) { + store.set(target as WritableAtom, value) +} + +function renderWithAtomStore( + children: ReactNode, + accessSettings?: { + environmentPolicies: EnvironmentAccessPolicy[] + }, +) { + const store = createStore() + store.set(setNextRouteStateAtom, { + pathname: '/deployments/app-instance-1/access', + params: { appInstanceId: 'app-instance-1' }, + }) + setTestAtom(store, accessSettingsAtom, accessSettings) + setTestAtom(store, accessSettingsIsLoadingAtom, false) + setTestAtom(store, accessSettingsIsErrorAtom, false) + + return render({children}) } function createEnvironment(overrides: Partial = {}): Environment { @@ -110,10 +134,6 @@ function createEnvironmentAccessPolicy(): EnvironmentAccessPolicy { describe('EnvironmentPermissionRow', () => { beforeEach(() => { vi.clearAllMocks() - mockUseAtomValue.mockImplementation((atom) => { - if (atom === deploymentRouteAppInstanceIdAtom) return 'app-instance-1' - return undefined - }) mockMutate.mockImplementation((_variables: unknown, options?: { onError?: () => void }) => { options?.onError?.() }) @@ -248,21 +268,12 @@ describe('EnvironmentPermissionRow', () => { describe('AccessPermissionsSection', () => { beforeEach(() => { vi.clearAllMocks() - mockUseAtomValue.mockImplementation((atom) => { - if (atom === deploymentRouteAppInstanceIdAtom) return 'app-instance-1' - if (atom === accessSettingsAtom) { - return { - environmentPolicies: [createEnvironmentAccessPolicy()], - } - } - if (atom === accessSettingsIsLoadingAtom) return false - if (atom === accessSettingsIsErrorAtom) return false - return undefined - }) }) it('should render permission rows without column headers', () => { - renderWithAtomStore() + renderWithAtomStore(, { + environmentPolicies: [createEnvironmentAccessPolicy()], + }) expect(screen.getByText('Production')).toBeInTheDocument() expect( diff --git a/web/features/deployments/detail/api-tokens/__tests__/state.spec.ts b/web/features/deployments/detail/api-tokens/__tests__/state.spec.ts index 36412b98fe0..d7134e95562 100644 --- a/web/features/deployments/detail/api-tokens/__tests__/state.spec.ts +++ b/web/features/deployments/detail/api-tokens/__tests__/state.spec.ts @@ -1,8 +1,7 @@ -import type { Getter } from 'jotai' import { skipToken } from '@tanstack/react-query' -import { atom, createStore } from 'jotai' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { setNextRouteStateAtom } from '@/app/components/next-route-state/atoms' +import { createQueryAtomTestStore } from '@/test/query-atom' type QueryOptions = { enabled?: boolean @@ -10,27 +9,21 @@ type QueryOptions = { queryKey?: readonly unknown[] } -vi.mock('jotai-tanstack-query', () => ({ - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => ({ - ...createOptions(get), - data: undefined, - isError: false, - isFetching: false, - isLoading: false, - isSuccess: false, - })), -})) +const mockDeveloperApiSettingsQueryOptions = vi.hoisted(() => vi.fn()) vi.mock('@/service/client', () => ({ consoleQuery: { enterprise: { accessService: { getDeveloperApiSettings: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['getDeveloperApiSettings', options.input], - }), + queryOptions: (options: QueryOptions) => { + mockDeveloperApiSettingsQueryOptions(options) + return { + ...options, + queryKey: ['getDeveloperApiSettings', options.input], + queryFn: async () => undefined, + } + }, }, }, }, @@ -42,7 +35,7 @@ async function loadState() { } function setDeploymentRoute( - store: ReturnType, + store: ReturnType['store'], appInstanceId = 'app-instance-1', ) { store.set(setNextRouteStateAtom, { @@ -52,18 +45,24 @@ function setDeploymentRoute( } describe('deployment API tokens state', () => { + beforeEach(() => { + mockDeveloperApiSettingsQueryOptions.mockClear() + }) + it('should gate API token queries until a route app instance exists', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() - expect(store.get(state.developerApiSettingsQueryAtom)).toMatchObject({ + store.get(state.developerApiSettingsQueryAtom) + expect(mockDeveloperApiSettingsQueryOptions).toHaveBeenLastCalledWith({ enabled: false, input: skipToken, }) setDeploymentRoute(store) + store.get(state.developerApiSettingsQueryAtom) - expect(store.get(state.developerApiSettingsQueryAtom)).toMatchObject({ + expect(mockDeveloperApiSettingsQueryOptions).toHaveBeenLastCalledWith({ enabled: true, input: { params: { appInstanceId: 'app-instance-1' } }, }) diff --git a/web/features/deployments/detail/api-tokens/api-keys/__tests__/api-key-generate-menu.spec.tsx b/web/features/deployments/detail/api-tokens/api-keys/__tests__/api-key-generate-menu.spec.tsx index db1e9c2d684..21044e7dd7a 100644 --- a/web/features/deployments/detail/api-tokens/api-keys/__tests__/api-key-generate-menu.spec.tsx +++ b/web/features/deployments/detail/api-tokens/api-keys/__tests__/api-key-generate-menu.spec.tsx @@ -1,19 +1,12 @@ import type { Environment } from '@dify/contracts/enterprise/types.gen' +import type { ReactNode } from 'react' import { fireEvent, render, screen } from '@testing-library/react' +import { createStore, Provider as JotaiProvider } from 'jotai' import { describe, expect, it, vi } from 'vitest' -import { deploymentRouteAppInstanceIdAtom } from '../../../../route-state' +import { setNextRouteStateAtom } from '@/app/components/next-route-state/atoms' import { ApiKeyGenerateMenu } from '../api-key-generate-menu' const mockMutate = vi.hoisted(() => vi.fn()) -const mockUseAtomValue = vi.hoisted(() => vi.fn()) - -vi.mock('jotai', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - useAtomValue: mockUseAtomValue, - } -}) vi.mock('@tanstack/react-query', () => ({ useMutation: () => ({ @@ -41,17 +34,30 @@ function createEnvironment(): Environment { } as Environment } +function renderMenu(appInstanceId?: string) { + const store = createStore() + store.set(setNextRouteStateAtom, { + pathname: '/deployments/app-instance-1/api-tokens', + params: appInstanceId ? { appInstanceId } : {}, + }) + + const wrapper = ({ children }: { children: ReactNode }) => ( + {children} + ) + + return render( + , + { wrapper }, + ) +} + describe('ApiKeyGenerateMenu', () => { beforeEach(() => { vi.clearAllMocks() - mockUseAtomValue.mockImplementation((atom) => { - if (atom === deploymentRouteAppInstanceIdAtom) return 'app-instance-1' - return undefined - }) }) it('should show the required name error when submitting an empty name', () => { - render() + renderMenu('app-instance-1') fireEvent.click(screen.getByRole('button', { name: 'deployments.access.api.newKey' })) fireEvent.change(screen.getByLabelText('deployments.access.api.nameLabel'), { @@ -64,7 +70,7 @@ describe('ApiKeyGenerateMenu', () => { }) it('should clear the required name error when typing a valid name', () => { - render() + renderMenu('app-instance-1') fireEvent.click(screen.getByRole('button', { name: 'deployments.access.api.newKey' })) const nameInput = screen.getByLabelText('deployments.access.api.nameLabel') @@ -83,9 +89,7 @@ describe('ApiKeyGenerateMenu', () => { }) it('should disable the trigger when route app instance is missing', () => { - mockUseAtomValue.mockReturnValue(undefined) - - render() + renderMenu(undefined) expect(screen.getByRole('button', { name: 'deployments.access.api.newKey' })).toBeDisabled() }) diff --git a/web/features/deployments/detail/overview/__tests__/state.spec.ts b/web/features/deployments/detail/overview/__tests__/state.spec.ts index 5a058fadf73..d97c2e0fd3e 100644 --- a/web/features/deployments/detail/overview/__tests__/state.spec.ts +++ b/web/features/deployments/detail/overview/__tests__/state.spec.ts @@ -1,8 +1,7 @@ -import type { Getter } from 'jotai' import { skipToken } from '@tanstack/react-query' -import { atom, createStore } from 'jotai' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { setNextRouteStateAtom } from '@/app/components/next-route-state/atoms' +import { createQueryAtomTestStore } from '@/test/query-atom' type QueryOptions = { enabled?: boolean @@ -10,27 +9,21 @@ type QueryOptions = { queryKey?: readonly unknown[] } -vi.mock('jotai-tanstack-query', () => ({ - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => ({ - ...createOptions(get), - data: undefined, - isError: false, - isFetching: false, - isLoading: false, - isSuccess: false, - })), -})) +const mockOverviewQueryOptions = vi.hoisted(() => vi.fn()) vi.mock('@/service/client', () => ({ consoleQuery: { enterprise: { appInstanceService: { getAppInstanceOverview: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['getAppInstanceOverview', options.input], - }), + queryOptions: (options: QueryOptions) => { + mockOverviewQueryOptions(options) + return { + ...options, + queryKey: ['getAppInstanceOverview', options.input], + queryFn: async () => undefined, + } + }, }, }, }, @@ -42,7 +35,7 @@ async function loadState() { } function setDeploymentRoute( - store: ReturnType, + store: ReturnType['store'], appInstanceId = 'app-instance-1', ) { store.set(setNextRouteStateAtom, { @@ -52,11 +45,17 @@ function setDeploymentRoute( } describe('deployment overview state', () => { + beforeEach(() => { + mockOverviewQueryOptions.mockClear() + }) + it('should disable overview query with skipToken until route state is ready', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() - expect(store.get(state.deploymentOverviewQueryAtom)).toMatchObject({ + store.get(state.deploymentOverviewQueryAtom) + + expect(mockOverviewQueryOptions).toHaveBeenLastCalledWith({ enabled: false, input: skipToken, }) @@ -64,11 +63,12 @@ describe('deployment overview state', () => { it('should build overview query input from route identity', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() setDeploymentRoute(store) + store.get(state.deploymentOverviewQueryAtom) - expect(store.get(state.deploymentOverviewQueryAtom)).toMatchObject({ + expect(mockOverviewQueryOptions).toHaveBeenLastCalledWith({ enabled: true, input: { params: { appInstanceId: 'app-instance-1' } }, }) diff --git a/web/features/deployments/detail/releases/__tests__/state.spec.ts b/web/features/deployments/detail/releases/__tests__/state.spec.ts index c3693e93893..0cfb0fc8e72 100644 --- a/web/features/deployments/detail/releases/__tests__/state.spec.ts +++ b/web/features/deployments/detail/releases/__tests__/state.spec.ts @@ -1,8 +1,7 @@ -import type { Getter } from 'jotai' import { skipToken } from '@tanstack/react-query' -import { atom, createStore } from 'jotai' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { setNextRouteStateAtom } from '@/app/components/next-route-state/atoms' +import { createQueryAtomTestStore } from '@/test/query-atom' type QueryOptions = { enabled?: boolean @@ -11,27 +10,21 @@ type QueryOptions = { queryKey?: readonly unknown[] } -vi.mock('jotai-tanstack-query', () => ({ - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => ({ - ...createOptions(get), - data: undefined, - isError: false, - isFetching: false, - isLoading: false, - isSuccess: false, - })), -})) +const mockReleaseSummariesQueryOptions = vi.hoisted(() => vi.fn()) vi.mock('@/service/client', () => ({ consoleQuery: { enterprise: { releaseService: { listReleaseSummaries: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['listReleaseSummaries', options.input], - }), + queryOptions: (options: QueryOptions) => { + mockReleaseSummariesQueryOptions(options) + return { + ...options, + queryKey: ['listReleaseSummaries', options.input], + queryFn: async () => undefined, + } + }, }, }, }, @@ -43,7 +36,7 @@ async function loadState() { } function setDeploymentRoute( - store: ReturnType, + store: ReturnType['store'], appInstanceId = 'app-instance-1', ) { store.set(setNextRouteStateAtom, { @@ -53,39 +46,49 @@ function setDeploymentRoute( } describe('deployment releases state', () => { + beforeEach(() => { + mockReleaseSummariesQueryOptions.mockClear() + }) + it('should gate release history query until route state is ready', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() - expect(store.get(state.releaseHistoryQueryAtom)).toMatchObject({ - enabled: false, - input: skipToken, - }) + store.get(state.releaseHistoryQueryAtom) + expect(mockReleaseSummariesQueryOptions).toHaveBeenLastCalledWith( + expect.objectContaining({ + enabled: false, + input: skipToken, + }), + ) }) it('should build release history input from the current page', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() setDeploymentRoute(store) store.set(state.setReleaseHistoryCurrentPageAtom, -1) expect(store.get(state.releaseHistoryCurrentPageAtom)).toBe(0) store.set(state.setReleaseHistoryCurrentPageAtom, 2) - expect(store.get(state.releaseHistoryQueryAtom)).toMatchObject({ - enabled: true, - input: { - params: { appInstanceId: 'app-instance-1' }, - query: { - pageNumber: 3, - }, - }, - }) + store.get(state.releaseHistoryQueryAtom) + expect(mockReleaseSummariesQueryOptions).toHaveBeenLastCalledWith( + expect.objectContaining({ + enabled: true, + input: expect.objectContaining({ + params: { appInstanceId: 'app-instance-1' }, + query: expect.objectContaining({ + pageNumber: 3, + }), + }), + }), + ) }) it('should adjust the release page only when deleting the last row on a later page', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() store.set(state.setReleaseHistoryCurrentPageAtom, 2) store.set(state.adjustReleaseHistoryPageAfterDeleteAtom, 2) diff --git a/web/features/deployments/detail/releases/release-actions/__tests__/state.spec.ts b/web/features/deployments/detail/releases/release-actions/__tests__/state.spec.ts index 09450b01303..57c7ea7c9d6 100644 --- a/web/features/deployments/detail/releases/release-actions/__tests__/state.spec.ts +++ b/web/features/deployments/detail/releases/release-actions/__tests__/state.spec.ts @@ -1,8 +1,7 @@ -import type { Getter } from 'jotai' import { skipToken } from '@tanstack/react-query' -import { atom, createStore } from 'jotai' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { setNextRouteStateAtom } from '@/app/components/next-route-state/atoms' +import { createQueryAtomTestStore } from '@/test/query-atom' type QueryOptions = { enabled?: boolean @@ -10,35 +9,34 @@ type QueryOptions = { queryKey?: readonly unknown[] } -vi.mock('jotai-tanstack-query', () => ({ - atomWithQuery: (createOptions: (get: Getter) => QueryOptions) => - atom((get) => ({ - ...createOptions(get), - data: undefined, - isError: false, - isFetching: false, - isLoading: false, - isSuccess: false, - })), -})) +const mockGetAppInstanceQueryOptions = vi.hoisted(() => vi.fn()) +const mockListEnvironmentDeploymentsQueryOptions = vi.hoisted(() => vi.fn()) vi.mock('@/service/client', () => ({ consoleQuery: { enterprise: { appInstanceService: { getAppInstance: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['getAppInstance', options.input], - }), + queryOptions: (options: QueryOptions) => { + mockGetAppInstanceQueryOptions(options) + return { + ...options, + queryKey: ['getAppInstance', options.input], + queryFn: async () => undefined, + } + }, }, }, deploymentService: { listEnvironmentDeployments: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['listEnvironmentDeployments', options.input], - }), + queryOptions: (options: QueryOptions) => { + mockListEnvironmentDeploymentsQueryOptions(options) + return { + ...options, + queryKey: ['listEnvironmentDeployments', options.input], + queryFn: async () => undefined, + } + }, }, }, }, @@ -50,7 +48,7 @@ async function loadState() { } function setDeploymentRoute( - store: ReturnType, + store: ReturnType['store'], appInstanceId = 'app-instance-1', ) { store.set(setNextRouteStateAtom, { @@ -60,33 +58,43 @@ function setDeploymentRoute( } describe('deployment release actions state', () => { + beforeEach(() => { + mockGetAppInstanceQueryOptions.mockClear() + mockListEnvironmentDeploymentsQueryOptions.mockClear() + }) + it('should gate action queries until route and menu state are ready', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() - expect(store.get(state.deployReleaseMenuEnvironmentDeploymentsQueryAtom)).toMatchObject({ + store.get(state.deployReleaseMenuEnvironmentDeploymentsQueryAtom) + expect(mockListEnvironmentDeploymentsQueryOptions).toHaveBeenLastCalledWith({ enabled: false, input: skipToken, }) - expect(store.get(state.deployReleaseMenuAppInstanceQueryAtom)).toMatchObject({ + store.get(state.deployReleaseMenuAppInstanceQueryAtom) + expect(mockGetAppInstanceQueryOptions).toHaveBeenLastCalledWith({ enabled: false, input: skipToken, }) setDeploymentRoute(store) - expect(store.get(state.deployReleaseMenuEnvironmentDeploymentsQueryAtom)).toMatchObject({ + store.get(state.deployReleaseMenuEnvironmentDeploymentsQueryAtom) + expect(mockListEnvironmentDeploymentsQueryOptions).toHaveBeenLastCalledWith({ enabled: false, input: { params: { appInstanceId: 'app-instance-1' } }, }) store.set(state.deployReleaseMenuOpenAtom, true) - expect(store.get(state.deployReleaseMenuEnvironmentDeploymentsQueryAtom)).toMatchObject({ + store.get(state.deployReleaseMenuEnvironmentDeploymentsQueryAtom) + expect(mockListEnvironmentDeploymentsQueryOptions).toHaveBeenLastCalledWith({ enabled: true, input: { params: { appInstanceId: 'app-instance-1' } }, }) - expect(store.get(state.deployReleaseMenuAppInstanceQueryAtom)).toMatchObject({ + store.get(state.deployReleaseMenuAppInstanceQueryAtom) + expect(mockGetAppInstanceQueryOptions).toHaveBeenLastCalledWith({ enabled: true, input: { params: { appInstanceId: 'app-instance-1' } }, }) @@ -94,7 +102,7 @@ describe('deployment release actions state', () => { it('should open one secondary dialog at a time and close the menu', async () => { const state = await loadState() - const store = createStore() + const { store } = createQueryAtomTestStore() store.set(state.deployReleaseMenuOpenAtom, true) store.set(state.openDeleteReleaseDialogAtom) diff --git a/web/features/tag-management/__tests__/tag-filter.spec.tsx b/web/features/tag-management/__tests__/tag-filter.spec.tsx index f4430b50a74..77668a30088 100644 --- a/web/features/tag-management/__tests__/tag-filter.spec.tsx +++ b/web/features/tag-management/__tests__/tag-filter.spec.tsx @@ -1,6 +1,7 @@ import type { TagResponse as Tag } from '@dify/contracts/api/console/tags/types.gen' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import { TagFilter } from '../components/tag-filter' const { mockUseQueryData } = vi.hoisted(() => ({ @@ -15,48 +16,12 @@ vi.mock('@tanstack/react-query', () => ({ useQuery: () => ({ data: mockUseQueryData.current }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) const mockTags: Tag[] = [ { id: 'tag-1', name: 'Frontend', type: 'app', binding_count: '' }, diff --git a/web/features/tag-management/__tests__/tag-management-modal.spec.tsx b/web/features/tag-management/__tests__/tag-management-modal.spec.tsx index 802f281a541..964c5d863ad 100644 --- a/web/features/tag-management/__tests__/tag-management-modal.spec.tsx +++ b/web/features/tag-management/__tests__/tag-management-modal.spec.tsx @@ -1,8 +1,9 @@ import type { TagResponse as Tag } from '@dify/contracts/api/console/tags/types.gen' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import * as ReactI18next from 'react-i18next' +import { render } from '@/test/console/render' import { TagManagementModal } from '../components/tag-management-modal' const { mockNotify, mockToast } = vi.hoisted(() => { @@ -44,48 +45,12 @@ vi.mock('@tanstack/react-query', () => ({ }), })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@/service/client', () => ({ consoleQuery: { diff --git a/web/features/tag-management/__tests__/tag-search-content.spec.tsx b/web/features/tag-management/__tests__/tag-search-content.spec.tsx index 0a8bc67109b..7007c46cf04 100644 --- a/web/features/tag-management/__tests__/tag-search-content.spec.tsx +++ b/web/features/tag-management/__tests__/tag-search-content.spec.tsx @@ -1,9 +1,10 @@ import type { TagResponse as Tag, TagType } from '@dify/contracts/api/console/tags/types.gen' import type { TagComboboxItem } from '../components/tag-combobox-item' import { Combobox } from '@langgenius/dify-ui/combobox' -import { render, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useMemo, useState } from 'react' +import { render } from '@/test/console/render' import { isCreateTagOption } from '../components/tag-combobox-item' import { TagSearchContent } from '../components/tag-search-content' @@ -15,48 +16,12 @@ const mockWorkspacePermissionKeys = vi.hoisted(() => ({ value: ['app.tag.manage', 'dataset.tag.manage'] as string[], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) const i18n = { selectorPlaceholder: 'common.tag.selectorPlaceholder', diff --git a/web/features/tag-management/__tests__/tag-selector.spec.tsx b/web/features/tag-management/__tests__/tag-selector.spec.tsx index 6e0ae63bbe5..1277caab23b 100644 --- a/web/features/tag-management/__tests__/tag-selector.spec.tsx +++ b/web/features/tag-management/__tests__/tag-selector.spec.tsx @@ -1,7 +1,8 @@ import type { TagResponse as Tag } from '@dify/contracts/api/console/tags/types.gen' import type { ComponentProps } from 'react' -import { render, screen, waitFor } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { render } from '@/test/console/render' import { TagSelector } from '../components/tag-selector' const { mockToast } = vi.hoisted(() => { @@ -33,48 +34,12 @@ const mockWorkspacePermissionKeys = vi.hoisted(() => ({ value: ['app.tag.manage', 'dataset.tag.manage', 'snippets.create_and_modify'] as string[], })) -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys.value, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys.value, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - - return createAppContextStateJotaiMock(importOriginal) -}) vi.mock('@tanstack/react-query', () => ({ useQuery: () => ({ data: mockUseQueryData.current }), diff --git a/web/hooks/use-credential-permissions.spec.ts b/web/hooks/use-credential-permissions.spec.ts index ea6922b0e22..6738c483822 100644 --- a/web/hooks/use-credential-permissions.spec.ts +++ b/web/hooks/use-credential-permissions.spec.ts @@ -1,44 +1,14 @@ -import { renderHook } from '@testing-library/react' +import { renderHook } from '@/test/console/render' import { useCredentialPermissions } from './use-credential-permissions' let mockWorkspacePermissionKeys: string[] = [] -vi.mock('@/context/account-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + return createPermissionStateModuleMock(() => ({ workspacePermissionKeys: mockWorkspacePermissionKeys, })) }) -vi.mock('@/context/workspace-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/permission-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/version-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) -vi.mock('@/context/system-features-state', async (importOriginal) => { - const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateAtomMock(importOriginal, () => ({ - workspacePermissionKeys: mockWorkspacePermissionKeys, - })) -}) - -vi.mock('jotai', async (importOriginal) => { - const { createAppContextStateJotaiMock } = - await import('@/__tests__/utils/mock-app-context-state') - return createAppContextStateJotaiMock(importOriginal) -}) describe('useCredentialPermissions', () => { beforeEach(() => { diff --git a/web/hooks/use-document-title.spec.ts b/web/hooks/use-document-title.spec.ts index a227822dae6..0de0ae3f29d 100644 --- a/web/hooks/use-document-title.spec.ts +++ b/web/hooks/use-document-title.spec.ts @@ -1,21 +1,21 @@ -import { renderHookWithSystemFeatures } from '@/__tests__/utils/mock-system-features' +import { renderHookWithConsoleQuery } from '@/test/console/query-data' import useDocumentTitle from './use-document-title' describe('useDocumentTitle', () => { it('keeps the title empty while system features are pending', () => { - renderHookWithSystemFeatures(() => useDocumentTitle('Settings'), { systemFeatures: null }) + renderHookWithConsoleQuery(() => useDocumentTitle('Settings'), { systemFeatures: null }) expect(document.title).toBe('') }) it('uses the default product name', () => { - renderHookWithSystemFeatures(() => useDocumentTitle('Settings'), { + renderHookWithConsoleQuery(() => useDocumentTitle('Settings'), { systemFeatures: { branding: { enabled: false } }, }) expect(document.title).toBe('Settings - Dify') }) it('uses the configured product name with or without a page title', () => { - const { rerender } = renderHookWithSystemFeatures(({ title }) => useDocumentTitle(title), { + const { rerender } = renderHookWithConsoleQuery(({ title }) => useDocumentTitle(title), { initialProps: { title: 'Settings' }, systemFeatures: { branding: { enabled: true, application_title: 'Acme' } }, }) diff --git a/web/hooks/use-import-dsl.spec.tsx b/web/hooks/use-import-dsl.spec.tsx index f2d9a4f52c4..a0f1328e49f 100644 --- a/web/hooks/use-import-dsl.spec.tsx +++ b/web/hooks/use-import-dsl.spec.tsx @@ -1,5 +1,6 @@ -import { act, renderHook } from '@testing-library/react' +import { act } from '@testing-library/react' import { DSLImportMode, DSLImportStatus } from '@/models/app' +import { renderHookWithConsoleQuery } from '@/test/console/query-data' import { AppModeEnum } from '@/types/app' import { useImportDSL } from './use-import-dsl' @@ -11,10 +12,6 @@ const mockInvalidateAppList = vi.hoisted(() => vi.fn()) const mockSetNeedRefresh = vi.hoisted(() => vi.fn()) const mockGetRedirection = vi.hoisted(() => vi.fn()) const mockResolveImportedAppRedirectionTarget = vi.hoisted(() => vi.fn()) -const mockAtoms = vi.hoisted(() => ({ - userProfileId: {}, - workspacePermissionKeys: {}, -})) const toastMocks = vi.hoisted(() => ({ error: vi.fn(), success: vi.fn(), @@ -25,17 +22,6 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ toast: toastMocks, })) -vi.mock('@tanstack/react-query', () => ({ - useSuspenseQuery: () => ({ data: { rbac_enabled: false } }), -})) - -vi.mock('jotai', () => ({ - useAtomValue: (atom: object) => { - if (atom === mockAtoms.userProfileId) return 'user-1' - if (atom === mockAtoms.workspacePermissionKeys) return ['app.create_and_management'] - }, -})) - vi.mock('@/app/components/apps/storage', () => ({ useSetNeedRefreshAppList: () => mockSetNeedRefresh, })) @@ -46,17 +32,21 @@ vi.mock('@/app/components/workflow/plugin-dependency/hooks', () => ({ }), })) -vi.mock('@/context/account-state', () => ({ - userProfileIdAtom: mockAtoms.userProfileId, -})) +vi.mock('@/context/account-state', async () => { + const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('@/context/permission-state', () => ({ - workspacePermissionKeysAtom: mockAtoms.workspacePermissionKeys, -})) + return createAccountStateModuleMock(() => ({ + userProfile: { id: 'user-1' }, + })) +}) -vi.mock('@/features/system-features/client', () => ({ - systemFeaturesQueryOptions: () => ({}), -})) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') + + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: ['app.create_and_management'], + })) +}) vi.mock('@/next/navigation', () => ({ useRouter: () => ({ push: mockPush }), @@ -116,7 +106,7 @@ describe('useImportDSL', () => { mockImportDSL.mockResolvedValue(pendingResponse) mockImportDSLConfirm.mockResolvedValue(completedResponse) - const { result } = renderHook(() => useImportDSL()) + const { result } = renderHookWithConsoleQuery(() => useImportDSL()) await act(async () => { await result.current.handleImportDSL( diff --git a/web/hooks/use-timestamp.spec.ts b/web/hooks/use-timestamp.spec.ts index 44104d40406..d4b62f7411b 100644 --- a/web/hooks/use-timestamp.spec.ts +++ b/web/hooks/use-timestamp.spec.ts @@ -3,7 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { renderHook } from '@testing-library/react' import { createElement } from 'react' import { userProfileQueryOptions } from '@/features/account-profile/client' -import { createAccountProfileQueryWrapper } from '@/test/account-profile-query' +import { createAccountProfileQueryWrapper } from '@/test/console/account-profile' import useTimestamp from './use-timestamp' const mockUseLocale = vi.hoisted(() => vi.fn(() => 'en-US')) diff --git a/web/service/__tests__/use-plugins.spec.tsx b/web/service/__tests__/use-plugins.spec.tsx index d41b0c60ed3..6e5ae5b0c07 100644 --- a/web/service/__tests__/use-plugins.spec.tsx +++ b/web/service/__tests__/use-plugins.spec.tsx @@ -2,7 +2,7 @@ import type { PluginInstallationItemResponse } from '@dify/contracts/api/console import type { ReactNode } from 'react' import type { Permissions, PluginTaskStart } from '@/app/components/plugins/types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { act, renderHook, waitFor } from '@testing-library/react' +import { act, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { FormTypeEnum } from '@/app/components/base/form/types' import { @@ -15,6 +15,7 @@ import { PluginSource, TaskStatus, } from '@/app/components/plugins/types' +import { renderHook } from '@/test/console/render' import { normalizeInstalledPluginDetail, useInstalledPluginList, @@ -24,10 +25,9 @@ import { usePluginTaskList, } from '../use-plugins' -const { mockGet, mockPost, mockWorkspacePermissionKeysAtom } = vi.hoisted(() => ({ +const { mockGet, mockPost } = vi.hoisted(() => ({ mockGet: vi.fn(), mockPost: vi.fn(), - mockWorkspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'), })) vi.mock('@/service/base', () => ({ @@ -43,29 +43,13 @@ vi.mock('@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list', }), })) -vi.mock('@/context/account-state', () => ({ - workspacePermissionKeysAtom: mockWorkspacePermissionKeysAtom, -})) -vi.mock('@/context/workspace-state', () => ({ - workspacePermissionKeysAtom: mockWorkspacePermissionKeysAtom, -})) -vi.mock('@/context/permission-state', () => ({ - workspacePermissionKeysAtom: mockWorkspacePermissionKeysAtom, -})) -vi.mock('@/context/version-state', () => ({ - workspacePermissionKeysAtom: mockWorkspacePermissionKeysAtom, -})) -vi.mock('@/context/system-features-state', () => ({ - workspacePermissionKeysAtom: mockWorkspacePermissionKeysAtom, -})) +vi.mock('@/context/permission-state', async () => { + const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') -vi.mock('jotai', () => ({ - useAtomValue: (atom: unknown) => { - if (atom === mockWorkspacePermissionKeysAtom) return ['plugin.install'] - - throw new Error('Unexpected atom') - }, -})) + return createPermissionStateModuleMock(() => ({ + workspacePermissionKeys: ['plugin.install'], + })) +}) vi.mock('../use-tools', () => ({ useInvalidateAllBuiltInTools: () => vi.fn(), diff --git a/web/test/account-profile-query.ts b/web/test/account-profile-query.ts deleted file mode 100644 index 9bb5ca7861c..00000000000 --- a/web/test/account-profile-query.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen' -import type { QueryClient } from '@tanstack/react-query' -import type { ReactNode } from 'react' -import type { UserProfileWithMeta } from '@/features/account-profile/client' -import { QueryClientProvider, QueryClient as TanStackQueryClient } from '@tanstack/react-query' -import { createElement } from 'react' -import { userProfileQueryOptions } from '@/features/account-profile/client' - -const createMockAccountProfile = ( - overrides: Partial = {}, -): GetAccountProfileResponse => ({ - id: 'user-1', - name: 'Test User', - email: 'test@dify.ai', - avatar: '', - avatar_url: null, - is_password_set: false, - timezone: 'Asia/Shanghai', - ...overrides, -}) - -const createMockUserProfileResponse = ( - profile: Partial = {}, -): UserProfileWithMeta => ({ - profile: createMockAccountProfile(profile), - meta: { - currentVersion: null, - currentEnv: null, - }, -}) - -export const createAccountProfileQueryClient = ( - profile: Partial = {}, -) => { - const queryClient = new TanStackQueryClient({ - defaultOptions: { - queries: { - retry: false, - staleTime: Number.POSITIVE_INFINITY, - }, - }, - }) - - queryClient.setQueryData( - userProfileQueryOptions().queryKey, - createMockUserProfileResponse(profile), - ) - - return queryClient -} - -export const createAccountProfileQueryWrapper = ( - profile: Partial = {}, -) => { - const queryClient = createAccountProfileQueryClient(profile) - - return function AccountProfileQueryWrapper({ children }: { children: ReactNode }) { - return createElement(QueryClientProvider, { client: queryClient }, children) - } -} - -export function AccountProfileQueryProvider({ - children, - queryClient, -}: { - children: ReactNode - queryClient: QueryClient -}) { - return createElement(QueryClientProvider, { client: queryClient }, children) -} diff --git a/web/test/console/account-profile.ts b/web/test/console/account-profile.ts new file mode 100644 index 00000000000..0d3f84bbb13 --- /dev/null +++ b/web/test/console/account-profile.ts @@ -0,0 +1,85 @@ +import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen' +import type { QueryClient } from '@tanstack/react-query' +import type { RenderOptions } from '@testing-library/react' +import type { ReactElement } from 'react' +import type { UserProfileWithMeta } from '@/features/account-profile/client' +import { render } from '@testing-library/react' +import { createQueryClientWrapper } from '@/test/console/query-client' +import { createTestQueryClient } from '@/test/query-client' + +const accountProfileQueryKey = [ + ['console', 'account', 'profile', 'get'], + { type: 'query' }, +] as const + +const createAccountProfileFixture = ( + overrides: Partial = {}, +): GetAccountProfileResponse => ({ + id: 'user-1', + name: 'Test User', + email: 'test@dify.ai', + avatar: '', + avatar_url: null, + is_password_set: false, + timezone: 'Asia/Shanghai', + ...overrides, +}) + +const createMockUserProfileResponse = ( + profile: Partial = {}, +): UserProfileWithMeta => ({ + profile: createAccountProfileFixture(profile), + meta: { + currentVersion: null, + currentEnv: null, + }, +}) + +export const seedAccountProfileQuery = ( + queryClient: QueryClient, + profile: Partial = {}, +) => { + const data = createMockUserProfileResponse(profile) + queryClient.setQueryData(accountProfileQueryKey, data) + return data +} + +export const ensureAccountProfileQuery = ( + queryClient: QueryClient, + profile: Partial = {}, +) => { + const existingProfile = queryClient.getQueryData(accountProfileQueryKey) + if (existingProfile === undefined) return seedAccountProfileQuery(queryClient, profile) + + return existingProfile +} + +export const createAccountProfileQueryClient = ( + profile: Partial = {}, +) => { + const queryClient = createTestQueryClient() + + seedAccountProfileQuery(queryClient, profile) + + return queryClient +} + +export const createAccountProfileQueryWrapper = ( + profile: Partial = {}, +) => { + const queryClient = createAccountProfileQueryClient(profile) + return createQueryClientWrapper(queryClient) +} + +export const renderWithAccountProfile = ( + ui: ReactElement, + options: Omit & { + accountProfile?: Partial + } = {}, +) => { + const { accountProfile, ...renderOptions } = options + return render(ui, { + ...renderOptions, + wrapper: createAccountProfileQueryWrapper(accountProfile), + }) +} diff --git a/web/test/console/current-workspace.ts b/web/test/console/current-workspace.ts new file mode 100644 index 00000000000..bbbd1843ce3 --- /dev/null +++ b/web/test/console/current-workspace.ts @@ -0,0 +1,45 @@ +import type { PostWorkspacesCurrentResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import type { QueryClient, QueryKey } from '@tanstack/react-query' + +export const currentWorkspaceQueryKey = [ + ['console', 'workspaces', 'current', 'post'], + { type: 'query' }, +] as const + +const createCurrentWorkspaceFixture = ( + overrides: Partial = {}, +): PostWorkspacesCurrentResponse => ({ + id: 'workspace-1', + name: 'Workspace', + plan: '', + role: 'owner', + status: '', + created_at: 0, + trial_credits: 0, + trial_credits_used: 0, + trial_credits_exhausted_at: 0, + next_credit_reset_date: 0, + ...overrides, +}) + +export const seedCurrentWorkspaceQuery = ( + queryClient: QueryClient, + workspace: Partial = {}, + queryKey: QueryKey = currentWorkspaceQueryKey, +) => { + const data = createCurrentWorkspaceFixture(workspace) + queryClient.setQueryData(queryKey, data) + return data +} + +export const ensureCurrentWorkspaceQuery = ( + queryClient: QueryClient, + workspace: Partial = {}, + queryKey: QueryKey = currentWorkspaceQueryKey, +) => { + const existingWorkspace = queryClient.getQueryData(queryKey) + if (existingWorkspace === undefined) + return seedCurrentWorkspaceQuery(queryClient, workspace, queryKey) + + return existingWorkspace +} diff --git a/web/test/console/query-client.ts b/web/test/console/query-client.ts new file mode 100644 index 00000000000..4e314e8d986 --- /dev/null +++ b/web/test/console/query-client.ts @@ -0,0 +1,10 @@ +import type { QueryClient } from '@tanstack/react-query' +import type { ReactNode } from 'react' +import { createElement } from 'react' +import { QueryClientTestProvider } from '@/test/console/query-provider' + +export const createQueryClientWrapper = (queryClient: QueryClient) => { + return function QueryClientWrapper({ children }: { children: ReactNode }) { + return createElement(QueryClientTestProvider, { queryClient }, children) + } +} diff --git a/web/__tests__/utils/mock-system-features.tsx b/web/test/console/query-data.ts similarity index 55% rename from web/__tests__/utils/mock-system-features.tsx rename to web/test/console/query-data.ts index 3d9375d2891..bbc13448f28 100644 --- a/web/__tests__/utils/mock-system-features.tsx +++ b/web/test/console/query-data.ts @@ -1,4 +1,7 @@ +import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen' import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' +import type { PostWorkspacesCurrentResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import type { QueryClient } from '@tanstack/react-query' import type { RenderHookOptions, RenderHookResult, @@ -6,10 +9,21 @@ import type { RenderResult, } from '@testing-library/react' import type { ReactElement, ReactNode } from 'react' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, renderHook } from '@testing-library/react' import { defaultSystemFeatures } from '@/features/system-features/config' import { consoleQuery } from '@/service/client' +import { ensureAccountProfileQuery, seedAccountProfileQuery } from '@/test/console/account-profile' +import { + currentWorkspaceQueryKey, + ensureCurrentWorkspaceQuery, + seedCurrentWorkspaceQuery, +} from '@/test/console/current-workspace' +import { createQueryClientWrapper } from '@/test/console/query-client' +import { + ensureWorkspacePermissionsQuery, + seedWorkspacePermissionsQuery, +} from '@/test/console/workspace-permissions' +import { createTestQueryClient } from '@/test/query-client' type QueryKeyProvider = { queryKey: () => readonly unknown[] @@ -23,6 +37,10 @@ type AppDslVersionQueryProvider = { get?: QueryKeyProvider } +type CurrentWorkspaceQueryProvider = { + post?: QueryKeyProvider +} + const fallbackTrialModelsQueryKey = ['console', 'trialModels', 'get'] as const const fallbackAppDslVersionQueryKey = ['console', 'appDslVersion', 'get'] as const @@ -39,6 +57,16 @@ const getAppDslVersionQueryKey = () => { return appDslVersionQuery?.get?.queryKey() ?? fallbackAppDslVersionQueryKey } +const getCurrentWorkspaceQueryKey = () => { + const currentWorkspaceQuery = ( + consoleQuery as { + workspaces?: { current?: CurrentWorkspaceQueryProvider } + } + ).workspaces?.current + + return currentWorkspaceQuery?.post?.queryKey() ?? currentWorkspaceQueryKey +} + type DeepPartial = T extends Array ? Array @@ -90,18 +118,8 @@ const buildSystemFeatures = ( * mirrors the behaviour of an in-flight network request without touching the * real fetch layer. */ -export const createTestQueryClient = (): QueryClient => - new QueryClient({ - defaultOptions: { - queries: { - retry: false, - gcTime: 0, - staleTime: Infinity, - queryFn: () => new Promise(() => {}), - }, - mutations: { retry: false }, - }, - }) +export const createConsoleQueryClient = (): QueryClient => + createTestQueryClient(() => new Promise(() => {})) export const seedSystemFeatures = ( queryClient: QueryClient, @@ -112,15 +130,35 @@ export const seedSystemFeatures = ( return data } +const ensureSystemFeatures = (queryClient: QueryClient) => { + const queryKey = consoleQuery.systemFeatures.get.queryKey() + const existingSystemFeatures = queryClient.getQueryData(queryKey) + if (existingSystemFeatures === undefined) return seedSystemFeatures(queryClient) + + return existingSystemFeatures +} + +const seedPendingSystemFeatures = (queryClient: QueryClient) => { + void queryClient.prefetchQuery({ + queryKey: consoleQuery.systemFeatures.get.queryKey(), + queryFn: () => new Promise(() => {}), + }) +} + const seedTrialModels = (queryClient: QueryClient, trialModels: readonly string[] = []) => { queryClient.setQueryData(getTrialModelsQueryKey(), { trial_models: [...trialModels] }) } +const ensureTrialModels = (queryClient: QueryClient) => { + const queryKey = getTrialModelsQueryKey() + if (queryClient.getQueryData(queryKey) === undefined) seedTrialModels(queryClient) +} + export const seedAppDslVersion = (queryClient: QueryClient, appDslVersion = '0.6.0') => { queryClient.setQueryData(getAppDslVersionQueryKey(), { app_dsl_version: appDslVersion }) } -type SystemFeaturesTestOptions = { +export type ConsoleQueryTestOptions = { /** * Partial overrides for the systemFeatures payload. When omitted, the cache * is seeded with `defaultSystemFeatures` so consumers using @@ -128,7 +166,10 @@ type SystemFeaturesTestOptions = { * keep the systemFeatures query in the pending state. */ systemFeatures?: DeepPartial | null + accountProfile?: Partial | null + currentWorkspace?: Partial | null trialModels?: readonly string[] | null + workspacePermissionKeys?: readonly string[] | null /** * Seed the workflow clipboard DSL version query only for tests that need it. * Omit or pass `null` to leave it unseeded. @@ -137,45 +178,71 @@ type SystemFeaturesTestOptions = { queryClient?: QueryClient } -type SystemFeaturesWrapper = { +type ConsoleQueryWrapper = { queryClient: QueryClient systemFeatures: GetSystemFeaturesResponse | null wrapper: (props: { children: ReactNode }) => ReactElement } -export const createSystemFeaturesWrapper = ( - options: SystemFeaturesTestOptions = {}, -): SystemFeaturesWrapper => { - const queryClient = options.queryClient ?? createTestQueryClient() +export const createConsoleQueryWrapper = ( + options: ConsoleQueryTestOptions = {}, +): ConsoleQueryWrapper => { + const queryClient = options.queryClient ?? createConsoleQueryClient() + if (options.accountProfile !== null) { + if (options.accountProfile) seedAccountProfileQuery(queryClient, options.accountProfile) + else ensureAccountProfileQuery(queryClient, { timezone: 'UTC' }) + } + if (options.currentWorkspace !== null) { + const queryKey = getCurrentWorkspaceQueryKey() + if (options.currentWorkspace) + seedCurrentWorkspaceQuery(queryClient, options.currentWorkspace, queryKey) + else ensureCurrentWorkspaceQuery(queryClient, {}, queryKey) + } + if (options.workspacePermissionKeys !== null) { + if (options.workspacePermissionKeys) + seedWorkspacePermissionsQuery(queryClient, 'workspace-1', options.workspacePermissionKeys) + else ensureWorkspacePermissionsQuery(queryClient) + } const systemFeatures = - options.systemFeatures === null ? null : seedSystemFeatures(queryClient, options.systemFeatures) - if (options.trialModels !== undefined && options.trialModels !== null) - seedTrialModels(queryClient, options.trialModels) + options.systemFeatures === null + ? null + : options.systemFeatures + ? seedSystemFeatures(queryClient, options.systemFeatures) + : ensureSystemFeatures(queryClient) + if (options.systemFeatures === null) seedPendingSystemFeatures(queryClient) + if (options.trialModels !== null) { + if (options.trialModels) seedTrialModels(queryClient, options.trialModels) + else ensureTrialModels(queryClient) + } if (options.appDslVersion !== undefined && options.appDslVersion !== null) seedAppDslVersion(queryClient, options.appDslVersion) - const wrapper = ({ children }: { children: ReactNode }) => ( - {children} - ) + const wrapper = createQueryClientWrapper(queryClient) return { queryClient, systemFeatures, wrapper } } -export const renderWithSystemFeatures = ( +export const renderWithConsoleQuery = ( ui: ReactElement, - options: SystemFeaturesTestOptions & Omit = {}, + options: ConsoleQueryTestOptions & Omit = {}, ): RenderResult & { queryClient: QueryClient systemFeatures: GetSystemFeaturesResponse | null } => { const { systemFeatures: sf, + accountProfile, + currentWorkspace, trialModels, + workspacePermissionKeys, appDslVersion, queryClient: qc, ...renderOptions } = options - const { wrapper, queryClient, systemFeatures } = createSystemFeaturesWrapper({ + const { wrapper, queryClient, systemFeatures } = createConsoleQueryWrapper({ systemFeatures: sf, + accountProfile, + currentWorkspace, trialModels, + workspacePermissionKeys, appDslVersion, queryClient: qc, }) @@ -183,23 +250,29 @@ export const renderWithSystemFeatures = ( return { ...rendered, queryClient, systemFeatures } } -export const renderHookWithSystemFeatures = ( +export const renderHookWithConsoleQuery = ( callback: (props: Props) => Result, - options: SystemFeaturesTestOptions & Omit, 'wrapper'> = {}, + options: ConsoleQueryTestOptions & Omit, 'wrapper'> = {}, ): RenderHookResult & { queryClient: QueryClient systemFeatures: GetSystemFeaturesResponse | null } => { const { systemFeatures: sf, + accountProfile, + currentWorkspace, trialModels, + workspacePermissionKeys, appDslVersion, queryClient: qc, ...hookOptions } = options - const { wrapper, queryClient, systemFeatures } = createSystemFeaturesWrapper({ + const { wrapper, queryClient, systemFeatures } = createConsoleQueryWrapper({ systemFeatures: sf, + accountProfile, + currentWorkspace, trialModels, + workspacePermissionKeys, appDslVersion, queryClient: qc, }) diff --git a/web/test/console/query-provider.tsx b/web/test/console/query-provider.tsx new file mode 100644 index 00000000000..c7d3a05d2db --- /dev/null +++ b/web/test/console/query-provider.tsx @@ -0,0 +1,32 @@ +import type { QueryClient } from '@tanstack/react-query' +import type { ReactNode } from 'react' +import { QueryClientProvider } from '@tanstack/react-query' +import { createStore, Provider } from 'jotai' +import { queryClientAtom } from 'jotai-tanstack-query' +import { createElement, useLayoutEffect, useMemo } from 'react' +import { seedRegisteredConsoleStateFixture } from '@/test/console/state-fixture' + +export function QueryClientTestProvider({ + children, + queryClient, +}: { + children?: ReactNode + queryClient: QueryClient +}) { + const store = useMemo(() => { + const nextStore = createStore() + nextStore.set(queryClientAtom, queryClient) + seedRegisteredConsoleStateFixture(nextStore) + return nextStore + }, [queryClient]) + + useLayoutEffect(() => { + seedRegisteredConsoleStateFixture(store) + }) + + return createElement( + QueryClientProvider, + { client: queryClient }, + createElement(Provider, { store }, children), + ) +} diff --git a/web/test/console/render.ts b/web/test/console/render.ts new file mode 100644 index 00000000000..45d7a87854d --- /dev/null +++ b/web/test/console/render.ts @@ -0,0 +1,69 @@ +import type { + RenderHookOptions, + RenderHookResult, + RenderOptions, + RenderResult, +} from '@testing-library/react' +import type { ReactElement, ReactNode } from 'react' +import { + render as testingLibraryRender, + renderHook as testingLibraryRenderHook, +} from '@testing-library/react' +import { createStore, Provider } from 'jotai' +import { createElement } from 'react' +import { seedRegisteredConsoleStateFixture } from './state-fixture' + +const createConsoleStateWrapper = ( + store: ReturnType, + Wrapper?: React.JSXElementConstructor<{ children: ReactNode }>, +) => { + return function ConsoleStateWrapper({ children }: { children: ReactNode }) { + const content = Wrapper ? createElement(Wrapper, undefined, children) : children + return createElement(Provider, { store }, content) + } +} + +export const render = (ui: ReactElement, options: RenderOptions = {}): RenderResult => { + const store = createStore() + const hasConsoleStateFixture = seedRegisteredConsoleStateFixture(store) + if (!hasConsoleStateFixture) return testingLibraryRender(ui, options) + + const { wrapper, ...renderOptions } = options + const rendered = testingLibraryRender(ui, { + ...renderOptions, + wrapper: createConsoleStateWrapper(store, wrapper), + }) + const rerender = rendered.rerender + + return { + ...rendered, + rerender: (nextUi) => { + seedRegisteredConsoleStateFixture(store) + rerender(nextUi) + }, + } +} + +export const renderHook = ( + callback: (props: Props) => Result, + options: RenderHookOptions = {}, +): RenderHookResult => { + const store = createStore() + const hasConsoleStateFixture = seedRegisteredConsoleStateFixture(store) + if (!hasConsoleStateFixture) return testingLibraryRenderHook(callback, options) + + const { wrapper, ...renderOptions } = options + const rendered = testingLibraryRenderHook(callback, { + ...renderOptions, + wrapper: createConsoleStateWrapper(store, wrapper), + }) + const rerender = rendered.rerender + + return { + ...rendered, + rerender: (props) => { + seedRegisteredConsoleStateFixture(store) + rerender(props) + }, + } +} diff --git a/web/test/console/state-fixture.ts b/web/test/console/state-fixture.ts new file mode 100644 index 00000000000..204b2e01e4e --- /dev/null +++ b/web/test/console/state-fixture.ts @@ -0,0 +1,225 @@ +import type { createStore } from 'jotai' +import type { LangGeniusVersionInfo } from '@/context/app-context-types' +import type { ICurrentWorkspace } from '@/models/common' +import { atom } from 'jotai' +import { defaultSystemFeatures } from '@/features/system-features/config' + +export type ConsoleStateFixture = { + userProfile?: { + id?: string + name?: string + email?: string + avatar?: string | null + avatar_url?: string | null + is_password_set?: boolean + } | null + currentWorkspace?: + | ({ + id?: string + name?: string + } & Partial) + | null + isCurrentWorkspaceManager?: boolean + isCurrentWorkspaceOwner?: boolean + isCurrentWorkspaceEditor?: boolean + isCurrentWorkspaceDatasetOperator?: boolean + isLoadingCurrentWorkspace?: boolean + isLoadingWorkspacePermissionKeys?: boolean + workspacePermissionKeys?: string[] + datasetRbacEnabled?: boolean + langGeniusVersionInfo?: Partial + refreshUserProfile?: () => void + refreshCurrentWorkspace?: () => void +} + +type ConsoleStateFixtureResolver = () => ConsoleStateFixture +type JotaiStore = ReturnType +type ConsoleStateOwner = 'account' | 'workspace' | 'permission' | 'systemFeatures' | 'version' + +const defaultUserProfile = { + id: 'user-1', + name: 'User', + email: 'user@example.com', + avatar: '', + avatar_url: '', + is_password_set: true, +} + +const defaultCurrentWorkspace = { + id: 'workspace-1', + name: 'Workspace', + plan: '', + status: '', + created_at: 0, + role: 'owner', + providers: [], + trial_credits: 0, + trial_credits_used: 0, + trial_credits_exhausted_at: 0, + next_credit_reset_date: 0, +} satisfies ICurrentWorkspace + +const defaultLangGeniusVersionInfo = { + current_env: 'CLOUD', + current_version: '', + latest_version: '', + version: '', + release_date: '', + release_notes: '', + features: { + can_replace_logo: false, + model_load_balancing_enabled: false, + }, + can_auto_update: false, +} satisfies LangGeniusVersionInfo + +const userProfileAtom = atom(defaultUserProfile) +const userProfileIdAtom = atom((get) => get(userProfileAtom).id) +const userProfileEmailAtom = atom((get) => get(userProfileAtom).email) +const accountProfileMetaAtom = atom({ currentVersion: null, currentEnv: null }) +const refreshUserProfileCallbackAtom = atom({ callback: () => {} }) +const refreshUserProfileAtom = atom(null, (get) => get(refreshUserProfileCallbackAtom).callback()) + +const currentWorkspaceAtom = atom(defaultCurrentWorkspace) +const currentWorkspaceIdAtom = atom((get) => get(currentWorkspaceAtom).id) +const isCurrentWorkspaceManagerAtom = atom(false) +const isCurrentWorkspaceOwnerAtom = atom(false) +const isCurrentWorkspaceEditorAtom = atom(false) +const isCurrentWorkspaceDatasetOperatorAtom = atom(false) +const currentWorkspaceLoadingAtom = atom(false) +const refreshCurrentWorkspaceCallbackAtom = atom({ callback: () => {} }) +const refreshCurrentWorkspaceAtom = atom(null, (get) => + get(refreshCurrentWorkspaceCallbackAtom).callback(), +) + +const workspacePermissionKeysAtom = atom([]) +const workspacePermissionKeysLoadingAtom = atom(false) + +const systemFeaturesAtom = atom(defaultSystemFeatures) +const datasetRbacEnabledAtom = atom((get) => get(systemFeaturesAtom).rbac_enabled) + +const langGeniusVersionInfoAtom = atom(defaultLangGeniusVersionInfo) +const langGeniusCurrentVersionAtom = atom((get) => get(langGeniusVersionInfoAtom).current_version) + +const consoleStateFixtureResolvers: Partial< + Record +> = {} + +const registerConsoleStateFixture = ( + owner: ConsoleStateOwner, + getState: ConsoleStateFixtureResolver, +) => { + consoleStateFixtureResolvers[owner] = getState +} + +export const seedRegisteredConsoleStateFixture = (store: JotaiStore) => { + const resolvers = Object.values(consoleStateFixtureResolvers) + if (!resolvers.length) return false + + const state = Object.assign({}, ...resolvers.map((resolve) => resolve())) + store.set(userProfileAtom, { + ...defaultUserProfile, + ...state.userProfile, + }) + store.set(currentWorkspaceAtom, { + ...defaultCurrentWorkspace, + ...state.currentWorkspace, + }) + store.set(isCurrentWorkspaceManagerAtom, state.isCurrentWorkspaceManager ?? false) + store.set(isCurrentWorkspaceOwnerAtom, state.isCurrentWorkspaceOwner ?? false) + store.set(isCurrentWorkspaceEditorAtom, state.isCurrentWorkspaceEditor ?? false) + store.set(isCurrentWorkspaceDatasetOperatorAtom, state.isCurrentWorkspaceDatasetOperator ?? false) + store.set(currentWorkspaceLoadingAtom, state.isLoadingCurrentWorkspace ?? false) + store.set(workspacePermissionKeysAtom, state.workspacePermissionKeys ?? []) + store.set(workspacePermissionKeysLoadingAtom, state.isLoadingWorkspacePermissionKeys ?? false) + store.set(systemFeaturesAtom, { + ...defaultSystemFeatures, + rbac_enabled: state.datasetRbacEnabled ?? false, + }) + store.set(langGeniusVersionInfoAtom, { + ...defaultLangGeniusVersionInfo, + ...state.langGeniusVersionInfo, + }) + store.set(refreshUserProfileCallbackAtom, { callback: state.refreshUserProfile ?? (() => {}) }) + store.set(refreshCurrentWorkspaceCallbackAtom, { + callback: state.refreshCurrentWorkspace ?? (() => {}), + }) + + return true +} + +export const createAccountStateModuleMock = (getState: ConsoleStateFixtureResolver) => { + registerConsoleStateFixture('account', () => { + const state = getState() + return { + userProfile: state.userProfile, + refreshUserProfile: state.refreshUserProfile, + } + }) + return { + userProfileAtom, + userProfileIdAtom, + userProfileEmailAtom, + accountProfileMetaAtom, + refreshUserProfileAtom, + } +} + +export const createWorkspaceStateModuleMock = (getState: ConsoleStateFixtureResolver) => { + registerConsoleStateFixture('workspace', () => { + const state = getState() + return { + currentWorkspace: state.currentWorkspace, + isCurrentWorkspaceManager: state.isCurrentWorkspaceManager, + isCurrentWorkspaceOwner: state.isCurrentWorkspaceOwner, + isCurrentWorkspaceEditor: state.isCurrentWorkspaceEditor, + isCurrentWorkspaceDatasetOperator: state.isCurrentWorkspaceDatasetOperator, + isLoadingCurrentWorkspace: state.isLoadingCurrentWorkspace, + refreshCurrentWorkspace: state.refreshCurrentWorkspace, + } + }) + return { + currentWorkspaceAtom, + currentWorkspaceIdAtom, + isCurrentWorkspaceManagerAtom, + isCurrentWorkspaceOwnerAtom, + isCurrentWorkspaceEditorAtom, + isCurrentWorkspaceDatasetOperatorAtom, + currentWorkspaceLoadingAtom, + refreshCurrentWorkspaceAtom, + } +} + +export const createPermissionStateModuleMock = (getState: ConsoleStateFixtureResolver) => { + registerConsoleStateFixture('permission', () => { + const state = getState() + return { + workspacePermissionKeys: state.workspacePermissionKeys, + isLoadingWorkspacePermissionKeys: state.isLoadingWorkspacePermissionKeys, + } + }) + return { + workspacePermissionKeysAtom, + workspacePermissionKeysLoadingAtom, + } +} + +export const createSystemFeaturesStateModuleMock = (getState: ConsoleStateFixtureResolver) => { + registerConsoleStateFixture('systemFeatures', () => ({ + datasetRbacEnabled: getState().datasetRbacEnabled, + })) + return { + systemFeaturesAtom, + datasetRbacEnabledAtom, + } +} + +export const createVersionStateModuleMock = (getState: ConsoleStateFixtureResolver) => { + registerConsoleStateFixture('version', () => ({ + langGeniusVersionInfo: getState().langGeniusVersionInfo, + })) + return { + langGeniusVersionInfoAtom, + langGeniusCurrentVersionAtom, + } +} diff --git a/web/test/console/workspace-permissions.ts b/web/test/console/workspace-permissions.ts new file mode 100644 index 00000000000..15acd8ec127 --- /dev/null +++ b/web/test/console/workspace-permissions.ts @@ -0,0 +1,42 @@ +import type { QueryClient } from '@tanstack/react-query' +import type { PermissionKeysResponse } from '@/models/access-control' +import { workspacePermissionKeysQueryOptions } from '@/service/access-control/use-permission-keys' + +const createWorkspacePermissionsFixture = ( + permissionKeys: readonly string[] = [], +): PermissionKeysResponse => ({ + workspace: { + permission_keys: [...permissionKeys], + }, + app: { + default_permission_keys: [], + overrides: [], + }, + dataset: { + default_permission_keys: [], + overrides: [], + }, +}) + +export const seedWorkspacePermissionsQuery = ( + queryClient: QueryClient, + workspaceId = 'workspace-1', + permissionKeys: readonly string[] = [], +) => { + const data = createWorkspacePermissionsFixture(permissionKeys) + queryClient.setQueryData(workspacePermissionKeysQueryOptions(workspaceId).queryKey, data) + return data +} + +export const ensureWorkspacePermissionsQuery = ( + queryClient: QueryClient, + workspaceId = 'workspace-1', + permissionKeys: readonly string[] = [], +) => { + const queryKey = workspacePermissionKeysQueryOptions(workspaceId).queryKey + const existingPermissions = queryClient.getQueryData(queryKey) + if (existingPermissions === undefined) + return seedWorkspacePermissionsQuery(queryClient, workspaceId, permissionKeys) + + return existingPermissions +} diff --git a/web/test/nuqs-testing.tsx b/web/test/nuqs-testing.tsx index b5bf9fa83ce..414a86d1975 100644 --- a/web/test/nuqs-testing.tsx +++ b/web/test/nuqs-testing.tsx @@ -1,9 +1,9 @@ import type { UrlUpdateEvent } from 'nuqs/adapters/testing' import type { ComponentProps, ReactElement, ReactNode } from 'react' import type { Mock } from 'vitest' -import { render, renderHook } from '@testing-library/react' import { NuqsTestingAdapter } from 'nuqs/adapters/testing' import { vi } from 'vitest' +import { render, renderHook } from '@/test/console/render' type NuqsSearchParams = ComponentProps['searchParams'] type NuqsOnUrlUpdate = (event: UrlUpdateEvent) => void diff --git a/web/test/query-atom.ts b/web/test/query-atom.ts new file mode 100644 index 00000000000..7e055ea58f5 --- /dev/null +++ b/web/test/query-atom.ts @@ -0,0 +1,11 @@ +import { createStore } from 'jotai' +import { queryClientAtom } from 'jotai-tanstack-query' +import { createTestQueryClient } from '@/test/query-client' + +export const createQueryAtomTestStore = () => { + const queryClient = createTestQueryClient() + const store = createStore() + store.set(queryClientAtom, queryClient) + + return { queryClient, store } +} diff --git a/web/test/query-client.ts b/web/test/query-client.ts new file mode 100644 index 00000000000..bef5f24910f --- /dev/null +++ b/web/test/query-client.ts @@ -0,0 +1,17 @@ +import type { QueryFunction } from '@tanstack/react-query' +import { QueryClient } from '@tanstack/react-query' + +export const createTestQueryClient = (queryFn?: QueryFunction) => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: Number.POSITIVE_INFINITY, + staleTime: Number.POSITIVE_INFINITY, + ...(queryFn && { queryFn }), + }, + mutations: { + retry: false, + }, + }, + }) diff --git a/web/vitest.setup.ts b/web/vitest.setup.ts index e944975a0b8..322a77df942 100644 --- a/web/vitest.setup.ts +++ b/web/vitest.setup.ts @@ -1,83 +1,24 @@ -import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen' -import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' -import * as jestDomMatchers from '@testing-library/jest-dom/matchers' import { act, cleanup } from '@testing-library/react' -import { getDefaultStore } from 'jotai' -import { queryClientAtom } from 'jotai-tanstack-query' import * as React from 'react' -import { afterEach, beforeEach, expect, vi } from 'vitest' +import { afterEach, beforeEach, vi } from 'vitest' +import '@testing-library/jest-dom/vitest' import 'vitest-canvas-mock' -if (typeof expect.extend === 'function') { - expect.extend(jestDomMatchers) -} - ;( globalThis as typeof globalThis & { BASE_UI_ANIMATIONS_DISABLED: boolean } ).BASE_UI_ANIMATIONS_DISABLED = true -// Base UI waits for element animations while closing overlays. -if (typeof window !== 'undefined') { - if (typeof Element !== 'undefined' && !Element.prototype.getAnimations) - Element.prototype.getAnimations = () => [] +if (typeof Element !== 'undefined' && !Element.prototype.getAnimations) + Element.prototype.getAnimations = () => [] - if (!document.getAnimations) document.getAnimations = () => [] -} - -if (typeof globalThis.ResizeObserver === 'undefined') { - globalThis.ResizeObserver = class { - observe() { - return undefined - } - - unobserve() { - return undefined - } - - disconnect() { - return undefined - } - } -} - -// Mock IntersectionObserver for tests -if (typeof globalThis.IntersectionObserver === 'undefined') { - globalThis.IntersectionObserver = class { - readonly root: Element | Document | null = null - readonly rootMargin: string = '' - readonly scrollMargin: string = '' - readonly thresholds: ReadonlyArray = [] - constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) { - /* noop */ - } - observe(_target: Element) { - /* noop */ - } - unobserve(_target: Element) { - /* noop */ - } - disconnect() { - /* noop */ - } - takeRecords(): IntersectionObserverEntry[] { - return [] - } - } -} - -// Mock global fetch to prevent happy-dom from making real network calls -// (which would cause ECONNREFUSED errors against localhost:5001). -// Individual tests can still override via vi.spyOn(globalThis, 'fetch') or reassignment. -globalThis.fetch = vi.fn(() => - Promise.resolve( - new Response(JSON.stringify({}), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }), - ), -) as unknown as typeof fetch +const unexpectedFetchCalls: Parameters[] = [] +const unexpectedFetchMock = vi.fn((...args: Parameters) => { + unexpectedFetchCalls.push(args) + return new Promise(() => {}) +}) as typeof fetch +globalThis.fetch = unexpectedFetchMock afterEach(async () => { // Wrap cleanup in act() to flush pending React scheduler work @@ -86,16 +27,14 @@ afterEach(async () => { await act(async () => { cleanup() }) -}) -// mock custom clipboard hook - wraps writeTextToClipboard with fallback -vi.mock('foxact/use-clipboard', () => ({ - useClipboard: () => ({ - copy: vi.fn(), - copied: false, - reset: vi.fn(), - }), -})) + if (unexpectedFetchCalls.length) { + const requests = unexpectedFetchCalls.map(([input]) => + input instanceof Request ? input.url : String(input), + ) + throw new Error(`Unexpected fetch request(s): ${requests.join(', ')}`) + } +}) // mock zustand - auto-resets all stores after each test // Based on official Zustand testing guide: https://zustand.docs.pmnd.rs/guides/testing @@ -111,16 +50,6 @@ vi.mock('react-i18next', async () => { } }) -// Mock FloatingPortal to render children in the normal DOM flow -vi.mock('@floating-ui/react', async () => { - const actual = await vi.importActual('@floating-ui/react') - return { - ...actual, - FloatingPortal: ({ children }: { children: React.ReactNode }) => - React.createElement('div', { 'data-floating-ui-portal': true }, children), - } -}) - vi.mock('@monaco-editor/react', () => { const createEditorMock = () => { const focusListeners: Array<() => void> = [] @@ -142,13 +71,9 @@ vi.mock('@monaco-editor/react', () => { getLineContent: vi.fn(() => ''), })), getPosition: vi.fn(() => ({ lineNumber: 1, column: 1 })), - deltaDecorations: vi.fn(() => []), focus: vi.fn(() => { focusListeners.forEach((listener) => listener()) }), - setPosition: vi.fn(), - revealLine: vi.fn(), - trigger: vi.fn(), __blur: () => { blurListeners.forEach((listener) => listener()) }, @@ -213,137 +138,13 @@ vi.mock('@monaco-editor/react', () => { Editor: MonacoEditor, loader: { config: vi.fn(), - init: vi.fn().mockResolvedValue(monacoMock), }, } }) -// Mock localStorage for testing -const createMockLocalStorage = () => { - const storage: Record = {} - return { - getItem: vi.fn((key: string) => storage[key] || null), - setItem: vi.fn((key: string, value: string) => { - storage[key] = value - }), - removeItem: vi.fn((key: string) => { - delete storage[key] - }), - clear: vi.fn(() => { - Object.keys(storage).forEach((key) => delete storage[key]) - }), - get storage() { - return { ...storage } - }, - } -} - -let mockLocalStorage: ReturnType - -const testAccountProfileQueryKey = [ - ['console', 'account', 'profile', 'get'], - { type: 'query' }, -] as const - -const testSystemFeaturesQueryKey = [ - ['console', 'systemFeatures', 'get'], - { type: 'query' }, -] as const - -const testAccountProfile = { - profile: { - id: 'user-1', - name: 'Test User', - email: 'test@dify.ai', - avatar: '', - avatar_url: null, - is_password_set: false, - timezone: 'UTC', - }, - meta: { - currentVersion: null, - currentEnv: null, - }, -} satisfies { - profile: GetAccountProfileResponse - meta: { - currentVersion: string | null - currentEnv: string | null - } -} - -const testSystemFeatures = { - enable_app_deploy: false, - sso_enforced_for_signin: false, - sso_enforced_for_signin_protocol: '', - enable_marketplace: false, - enable_email_code_login: false, - enable_email_password_login: true, - enable_social_oauth_login: false, - enable_collaboration_mode: true, - is_allow_create_workspace: false, - is_allow_register: false, - is_email_setup: false, - enable_change_email: true, - max_plugin_package_size: 15728640, - license: { - status: 'none', - expired_at: '', - workspaces: { - enabled: false, - size: 0, - limit: 0, - }, - seats: { - enabled: false, - size: 0, - limit: 0, - }, - }, - branding: { - enabled: false, - login_page_logo: '', - workspace_logo: '', - favicon: '', - application_title: '', - }, - webapp_auth: { - enabled: false, - allow_sso: false, - sso_config: { - protocol: '', - }, - allow_email_code_login: false, - allow_email_password_login: false, - }, - plugin_installation_permission: { - plugin_installation_scope: 'all', - restrict_to_marketplace_only: false, - }, - plugin_manager: { - enabled: false, - }, - rbac_enabled: false, - enable_creators_platform: false, - enable_trial_app: false, - enable_explore_banner: false, - enable_learn_app: true, -} satisfies GetSystemFeaturesResponse - -const seedResolvedAppContextQueries = () => { - const queryClient = getDefaultStore().get(queryClientAtom) - - queryClient.setQueryData(testAccountProfileQueryKey, testAccountProfile) - queryClient.setQueryData(testSystemFeaturesQueryKey, testSystemFeatures) -} - beforeEach(() => { - vi.clearAllMocks() - seedResolvedAppContextQueries() - mockLocalStorage = createMockLocalStorage() - Object.defineProperty(globalThis, 'localStorage', { - value: mockLocalStorage, - writable: true, - configurable: true, - }) + unexpectedFetchCalls.length = 0 + vi.mocked(unexpectedFetchMock).mockClear() + globalThis.fetch = unexpectedFetchMock + if (typeof localStorage !== 'undefined') localStorage.clear() })