From 6da802eb2a69c2c2c4a655b964cd6dcf2a6480c1 Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Mon, 16 Mar 2026 18:17:21 +0800 Subject: [PATCH] refactor(custom): reorganize web app brand module and raise coverage threshold (#33531) Co-authored-by: CodingOnStar Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../custom-page/__tests__/index.spec.tsx | 571 ++++-------------- .../__tests__/index.spec.tsx | 263 ++++---- .../__tests__/chat-preview-card.spec.tsx | 31 + .../__tests__/powered-by-brand.spec.tsx | 41 ++ .../__tests__/workflow-preview-card.spec.tsx | 32 + .../components/chat-preview-card.tsx | 78 +++ .../components/powered-by-brand.tsx | 31 + .../components/workflow-preview-card.tsx | 64 ++ .../__tests__/use-web-app-brand.spec.tsx | 385 ++++++++++++ .../hooks/use-web-app-brand.ts | 121 ++++ .../custom/custom-web-app-brand/index.tsx | 249 +------- .../{ => __tests__}/header-wrapper.spec.tsx | 2 +- .../header/{ => __tests__}/index.spec.tsx | 2 +- .../maintenance-notice.spec.tsx} | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/compliance.spec.tsx | 4 +- .../{ => __tests__}/index.spec.tsx | 6 +- .../{ => __tests__}/support.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/constants.spec.ts | 2 +- .../account-setting/__tests__/index.spec.tsx | 346 +++++++++++ .../{ => __tests__}/menu-dialog.spec.tsx | 2 +- .../{ => __tests__}/empty.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/item.spec.tsx | 2 +- .../{ => __tests__}/modal.spec.tsx | 2 +- .../{ => __tests__}/selector.spec.tsx | 2 +- .../collapse/{ => __tests__}/index.spec.tsx | 4 +- .../{ => __tests__}/card.spec.tsx | 8 +- .../{ => __tests__}/configure.spec.tsx | 4 +- .../{ => __tests__}/index.spec.tsx | 8 +- .../install-from-marketplace.spec.tsx | 8 +- .../{ => __tests__}/item.spec.tsx | 4 +- .../{ => __tests__}/operator.spec.tsx | 4 +- .../use-data-source-auth-update.spec.ts | 2 +- .../use-marketplace-all-plugins.spec.ts | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../operate/{ => __tests__}/index.spec.tsx | 2 +- .../config-firecrawl-modal.spec.tsx | 2 +- .../config-jina-reader-modal.spec.tsx | 2 +- .../config-watercrawl-modal.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/config-item.spec.tsx | 8 +- .../panel/{ => __tests__}/index.spec.tsx | 8 +- .../header/account-setting/index.spec.tsx | 334 ---------- .../{ => __tests__}/KeyInput.spec.tsx | 4 +- .../{ => __tests__}/Operate.spec.tsx | 2 +- .../{ => __tests__}/ValidateStatus.spec.tsx | 2 +- .../{ => __tests__}/declarations.spec.ts | 2 +- .../{ => __tests__}/hooks.spec.ts | 4 +- .../{ => __tests__}/index.spec.tsx | 6 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 16 +- .../{ => __tests__}/invite-button.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/role-selector.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/invitation-link.spec.tsx | 2 +- .../operation/{ => __tests__}/index.spec.tsx | 2 +- .../transfer-ownership.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 4 +- .../{ => __tests__}/member-selector.spec.tsx | 2 +- .../{ => __tests__}/hooks.spec.ts | 8 +- .../{ => __tests__}/index.spec.tsx | 14 +- .../install-from-marketplace.spec.tsx | 8 +- .../{ => __tests__}/utils.spec.ts | 6 +- .../add-credential-in-load-balancing.spec.tsx | 4 +- .../{ => __tests__}/add-custom-model.spec.tsx | 8 +- .../{ => __tests__}/config-model.spec.tsx | 2 +- .../{ => __tests__}/config-provider.spec.tsx | 6 +- .../credential-selector.spec.tsx | 4 +- .../manage-custom-model-credentials.spec.tsx | 30 +- ...itch-credential-in-load-balancing.spec.tsx | 4 +- .../{ => __tests__}/authorized-item.spec.tsx | 10 +- .../{ => __tests__}/credential-item.spec.tsx | 4 +- .../authorized/{ => __tests__}/index.spec.tsx | 10 +- .../use-auth-service.spec.tsx} | 6 +- .../hooks/{ => __tests__}/use-auth.spec.tsx | 8 +- .../use-credential-data.spec.tsx | 8 +- .../use-credential-status.spec.tsx | 4 +- .../use-custom-models.spec.tsx | 4 +- .../use-model-form-schemas.spec.tsx | 6 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../model-icon/{ => __tests__}/index.spec.tsx | 8 +- .../model-modal/Input.test.tsx | 16 - .../__snapshots__/Input.test.tsx.snap | 24 - .../model-modal/{ => __tests__}/Form.spec.tsx | 24 +- .../{ => __tests__}/Input.spec.tsx | 17 +- .../{ => __tests__}/index.spec.tsx | 12 +- .../model-name/{ => __tests__}/index.spec.tsx | 6 +- .../agent-model-trigger.spec.tsx | 14 +- .../configuration-button.spec.tsx | 4 +- .../{ => __tests__}/index.spec.tsx | 12 +- .../{ => __tests__}/model-display.spec.tsx | 4 +- .../{ => __tests__}/parameter-item.spec.tsx | 6 +- .../presets-parameter.spec.tsx | 2 +- .../status-indicators.spec.tsx | 2 +- .../{ => __tests__}/trigger.spec.tsx | 8 +- .../deprecated-model-trigger.spec.tsx | 4 +- .../{ => __tests__}/empty-trigger.spec.tsx | 2 +- .../{ => __tests__}/feature-icon.spec.tsx | 4 +- .../{ => __tests__}/index.spec.tsx | 14 +- .../{ => __tests__}/model-trigger.spec.tsx | 14 +- .../{ => __tests__}/popup-item.spec.tsx | 16 +- .../{ => __tests__}/popup.spec.tsx | 12 +- .../{ => __tests__}/add-model-button.spec.tsx | 2 +- .../{ => __tests__}/cooldown-timer.spec.tsx | 2 +- .../{ => __tests__}/credential-panel.spec.tsx | 12 +- .../{ => __tests__}/index.spec.tsx | 14 +- .../{ => __tests__}/model-list-item.spec.tsx | 14 +- .../{ => __tests__}/model-list.spec.tsx | 8 +- .../model-load-balancing-configs.spec.tsx | 8 +- .../model-load-balancing-modal.spec.tsx | 16 +- .../priority-selector.spec.tsx | 2 +- .../{ => __tests__}/priority-use-tip.spec.tsx | 2 +- .../{ => __tests__}/quota-panel.spec.tsx | 6 +- .../{ => __tests__}/index.spec.tsx | 8 +- .../{ => __tests__}/index.spec.tsx | 10 +- .../{ => __tests__}/SerpapiPlugin.spec.tsx | 6 +- .../{ => __tests__}/index.spec.tsx | 6 +- .../plugin-page/{ => __tests__}/utils.spec.ts | 4 +- .../app-back/{ => __tests__}/index.spec.tsx | 2 +- .../app-nav/{ => __tests__}/index.spec.tsx | 4 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../env-nav/{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../indicator/{ => __tests__}/index.spec.tsx | 2 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../header/nav/{ => __tests__}/index.spec.tsx | 4 +- .../{ => __tests__}/index.spec.tsx | 4 +- .../plan-badge/{ => __tests__}/index.spec.tsx | 4 +- .../{ => __tests__}/index.spec.tsx | 2 +- .../tools-nav/{ => __tests__}/index.spec.tsx | 2 +- .../header/utils/{ => __tests__}/util.spec.ts | 2 +- web/eslint-suppressions.json | 5 - .../components-coverage-thresholds.mjs | 8 +- 140 files changed, 1782 insertions(+), 1486 deletions(-) create mode 100644 web/app/components/custom/custom-web-app-brand/components/__tests__/chat-preview-card.spec.tsx create mode 100644 web/app/components/custom/custom-web-app-brand/components/__tests__/powered-by-brand.spec.tsx create mode 100644 web/app/components/custom/custom-web-app-brand/components/__tests__/workflow-preview-card.spec.tsx create mode 100644 web/app/components/custom/custom-web-app-brand/components/chat-preview-card.tsx create mode 100644 web/app/components/custom/custom-web-app-brand/components/powered-by-brand.tsx create mode 100644 web/app/components/custom/custom-web-app-brand/components/workflow-preview-card.tsx create mode 100644 web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx create mode 100644 web/app/components/custom/custom-web-app-brand/hooks/use-web-app-brand.ts rename web/app/components/header/{ => __tests__}/header-wrapper.spec.tsx (98%) rename web/app/components/header/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/{ maintenance-notice.spec.tsx => __tests__/maintenance-notice.spec.tsx} (98%) rename web/app/components/header/account-about/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-dropdown/{ => __tests__}/compliance.spec.tsx (99%) rename web/app/components/header/account-dropdown/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-dropdown/{ => __tests__}/support.spec.tsx (99%) rename web/app/components/header/account-dropdown/workplace-selector/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/Integrations-page/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/{ => __tests__}/constants.spec.ts (98%) create mode 100644 web/app/components/header/account-setting/__tests__/index.spec.tsx rename web/app/components/header/account-setting/{ => __tests__}/menu-dialog.spec.tsx (98%) rename web/app/components/header/account-setting/api-based-extension-page/{ => __tests__}/empty.spec.tsx (95%) rename web/app/components/header/account-setting/api-based-extension-page/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/api-based-extension-page/{ => __tests__}/item.spec.tsx (99%) rename web/app/components/header/account-setting/api-based-extension-page/{ => __tests__}/modal.spec.tsx (99%) rename web/app/components/header/account-setting/api-based-extension-page/{ => __tests__}/selector.spec.tsx (98%) rename web/app/components/header/account-setting/collapse/{ => __tests__}/index.spec.tsx (97%) rename web/app/components/header/account-setting/data-source-page-new/{ => __tests__}/card.spec.tsx (98%) rename web/app/components/header/account-setting/data-source-page-new/{ => __tests__}/configure.spec.tsx (99%) rename web/app/components/header/account-setting/data-source-page-new/{ => __tests__}/index.spec.tsx (98%) rename web/app/components/header/account-setting/data-source-page-new/{ => __tests__}/install-from-marketplace.spec.tsx (96%) rename web/app/components/header/account-setting/data-source-page-new/{ => __tests__}/item.spec.tsx (98%) rename web/app/components/header/account-setting/data-source-page-new/{ => __tests__}/operator.spec.tsx (98%) rename web/app/components/header/account-setting/data-source-page-new/hooks/{ => __tests__}/use-data-source-auth-update.spec.ts (97%) rename web/app/components/header/account-setting/data-source-page-new/hooks/{ => __tests__}/use-marketplace-all-plugins.spec.ts (98%) rename web/app/components/header/account-setting/data-source-page/data-source-notion/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/data-source-page/data-source-notion/operate/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/data-source-page/data-source-website/{ => __tests__}/config-firecrawl-modal.spec.tsx (99%) rename web/app/components/header/account-setting/data-source-page/data-source-website/{ => __tests__}/config-jina-reader-modal.spec.tsx (99%) rename web/app/components/header/account-setting/data-source-page/data-source-website/{ => __tests__}/config-watercrawl-modal.spec.tsx (99%) rename web/app/components/header/account-setting/data-source-page/data-source-website/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/data-source-page/panel/{ => __tests__}/config-item.spec.tsx (97%) rename web/app/components/header/account-setting/data-source-page/panel/{ => __tests__}/index.spec.tsx (97%) delete mode 100644 web/app/components/header/account-setting/index.spec.tsx rename web/app/components/header/account-setting/key-validator/{ => __tests__}/KeyInput.spec.tsx (97%) rename web/app/components/header/account-setting/key-validator/{ => __tests__}/Operate.spec.tsx (98%) rename web/app/components/header/account-setting/key-validator/{ => __tests__}/ValidateStatus.spec.tsx (97%) rename web/app/components/header/account-setting/key-validator/{ => __tests__}/declarations.spec.ts (87%) rename web/app/components/header/account-setting/key-validator/{ => __tests__}/hooks.spec.ts (95%) rename web/app/components/header/account-setting/key-validator/{ => __tests__}/index.spec.tsx (97%) rename web/app/components/header/account-setting/language-page/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/members-page/{ => __tests__}/index.spec.tsx (97%) rename web/app/components/header/account-setting/members-page/{ => __tests__}/invite-button.spec.tsx (98%) rename web/app/components/header/account-setting/members-page/edit-workspace-modal/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/members-page/invite-modal/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/members-page/invite-modal/{ => __tests__}/role-selector.spec.tsx (98%) rename web/app/components/header/account-setting/members-page/invited-modal/{ => __tests__}/index.spec.tsx (98%) rename web/app/components/header/account-setting/members-page/invited-modal/{ => __tests__}/invitation-link.spec.tsx (97%) rename web/app/components/header/account-setting/members-page/operation/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/members-page/operation/{ => __tests__}/transfer-ownership.spec.tsx (98%) rename web/app/components/header/account-setting/members-page/transfer-ownership-modal/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/account-setting/members-page/transfer-ownership-modal/{ => __tests__}/member-selector.spec.tsx (99%) rename web/app/components/header/account-setting/model-provider-page/{ => __tests__}/hooks.spec.ts (99%) rename web/app/components/header/account-setting/model-provider-page/{ => __tests__}/index.spec.tsx (97%) rename web/app/components/header/account-setting/model-provider-page/{ => __tests__}/install-from-marketplace.spec.tsx (94%) rename web/app/components/header/account-setting/model-provider-page/{ => __tests__}/utils.spec.ts (99%) rename web/app/components/header/account-setting/model-provider-page/model-auth/{ => __tests__}/add-credential-in-load-balancing.spec.tsx (97%) rename web/app/components/header/account-setting/model-provider-page/model-auth/{ => __tests__}/add-custom-model.spec.tsx (97%) rename web/app/components/header/account-setting/model-provider-page/model-auth/{ => __tests__}/config-model.spec.tsx (97%) rename web/app/components/header/account-setting/model-provider-page/model-auth/{ => __tests__}/config-provider.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/model-auth/{ => __tests__}/credential-selector.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/model-auth/{ => __tests__}/manage-custom-model-credentials.spec.tsx (79%) rename web/app/components/header/account-setting/model-provider-page/model-auth/{ => __tests__}/switch-credential-in-load-balancing.spec.tsx (98%) rename web/app/components/header/account-setting/model-provider-page/model-auth/authorized/{ => __tests__}/authorized-item.spec.tsx (95%) rename web/app/components/header/account-setting/model-provider-page/model-auth/authorized/{ => __tests__}/credential-item.spec.tsx (97%) rename web/app/components/header/account-setting/model-provider-page/model-auth/authorized/{ => __tests__}/index.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/model-auth/hooks/{use-auth-service..spec.tsx => __tests__/use-auth-service.spec.tsx} (96%) rename web/app/components/header/account-setting/model-provider-page/model-auth/hooks/{ => __tests__}/use-auth.spec.tsx (98%) rename web/app/components/header/account-setting/model-provider-page/model-auth/hooks/{ => __tests__}/use-credential-data.spec.tsx (93%) rename web/app/components/header/account-setting/model-provider-page/model-auth/hooks/{ => __tests__}/use-credential-status.spec.tsx (94%) rename web/app/components/header/account-setting/model-provider-page/model-auth/hooks/{ => __tests__}/use-custom-models.spec.tsx (91%) rename web/app/components/header/account-setting/model-provider-page/model-auth/hooks/{ => __tests__}/use-model-form-schemas.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/model-badge/{ => __tests__}/index.spec.tsx (95%) rename web/app/components/header/account-setting/model-provider-page/model-icon/{ => __tests__}/index.spec.tsx (96%) delete mode 100644 web/app/components/header/account-setting/model-provider-page/model-modal/Input.test.tsx delete mode 100644 web/app/components/header/account-setting/model-provider-page/model-modal/__snapshots__/Input.test.tsx.snap rename web/app/components/header/account-setting/model-provider-page/model-modal/{ => __tests__}/Form.spec.tsx (98%) rename web/app/components/header/account-setting/model-provider-page/model-modal/{ => __tests__}/Input.spec.tsx (91%) rename web/app/components/header/account-setting/model-provider-page/model-modal/{ => __tests__}/index.spec.tsx (98%) rename web/app/components/header/account-setting/model-provider-page/model-name/{ => __tests__}/index.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/model-parameter-modal/{ => __tests__}/agent-model-trigger.spec.tsx (93%) rename web/app/components/header/account-setting/model-provider-page/model-parameter-modal/{ => __tests__}/configuration-button.spec.tsx (86%) rename web/app/components/header/account-setting/model-provider-page/model-parameter-modal/{ => __tests__}/index.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/model-parameter-modal/{ => __tests__}/model-display.spec.tsx (89%) rename web/app/components/header/account-setting/model-provider-page/model-parameter-modal/{ => __tests__}/parameter-item.spec.tsx (98%) rename web/app/components/header/account-setting/model-provider-page/model-parameter-modal/{ => __tests__}/presets-parameter.spec.tsx (97%) rename web/app/components/header/account-setting/model-provider-page/model-parameter-modal/{ => __tests__}/status-indicators.spec.tsx (98%) rename web/app/components/header/account-setting/model-provider-page/model-parameter-modal/{ => __tests__}/trigger.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/model-selector/{ => __tests__}/deprecated-model-trigger.spec.tsx (94%) rename web/app/components/header/account-setting/model-provider-page/model-selector/{ => __tests__}/empty-trigger.spec.tsx (95%) rename web/app/components/header/account-setting/model-provider-page/model-selector/{ => __tests__}/feature-icon.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/model-selector/{ => __tests__}/index.spec.tsx (92%) rename web/app/components/header/account-setting/model-provider-page/model-selector/{ => __tests__}/model-trigger.spec.tsx (86%) rename web/app/components/header/account-setting/model-provider-page/model-selector/{ => __tests__}/popup-item.spec.tsx (94%) rename web/app/components/header/account-setting/model-provider-page/model-selector/{ => __tests__}/popup.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/add-model-button.spec.tsx (92%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/cooldown-timer.spec.tsx (95%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/credential-panel.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/index.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/model-list-item.spec.tsx (95%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/model-list.spec.tsx (97%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/model-load-balancing-configs.spec.tsx (98%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/model-load-balancing-modal.spec.tsx (98%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/priority-selector.spec.tsx (94%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/priority-use-tip.spec.tsx (97%) rename web/app/components/header/account-setting/model-provider-page/provider-added-card/{ => __tests__}/quota-panel.spec.tsx (97%) rename web/app/components/header/account-setting/model-provider-page/provider-icon/{ => __tests__}/index.spec.tsx (96%) rename web/app/components/header/account-setting/model-provider-page/system-model-selector/{ => __tests__}/index.spec.tsx (97%) rename web/app/components/header/account-setting/plugin-page/{ => __tests__}/SerpapiPlugin.spec.tsx (97%) rename web/app/components/header/account-setting/plugin-page/{ => __tests__}/index.spec.tsx (96%) rename web/app/components/header/account-setting/plugin-page/{ => __tests__}/utils.spec.ts (94%) rename web/app/components/header/app-back/{ => __tests__}/index.spec.tsx (97%) rename web/app/components/header/app-nav/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/app-selector/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/dataset-nav/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/env-nav/{ => __tests__}/index.spec.tsx (97%) rename web/app/components/header/explore-nav/{ => __tests__}/index.spec.tsx (97%) rename web/app/components/header/github-star/{ => __tests__}/index.spec.tsx (98%) rename web/app/components/header/indicator/{ => __tests__}/index.spec.tsx (98%) rename web/app/components/header/license-env/{ => __tests__}/index.spec.tsx (98%) rename web/app/components/header/nav/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/nav/nav-selector/{ => __tests__}/index.spec.tsx (98%) rename web/app/components/header/plan-badge/{ => __tests__}/index.spec.tsx (97%) rename web/app/components/header/plugins-nav/{ => __tests__}/index.spec.tsx (99%) rename web/app/components/header/tools-nav/{ => __tests__}/index.spec.tsx (98%) rename web/app/components/header/utils/{ => __tests__}/util.spec.ts (97%) 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 0da27e06a6..cdc35ba1eb 100644 --- a/web/app/components/custom/custom-page/__tests__/index.spec.tsx +++ b/web/app/components/custom/custom-page/__tests__/index.spec.tsx @@ -1,496 +1,179 @@ -import type { Mock } from 'vitest' +import type { AppContextValue } from '@/context/app-context' +import type { SystemFeatures } from '@/types/feature' import { render, screen } 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 { createMockProviderContextValue } from '@/__mocks__/provider-context' -import { contactSalesUrl } from '@/app/components/billing/config' +import { useToastContext } from '@/app/components/base/toast/context' +import { contactSalesUrl, defaultPlan } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' +import { + initialLangGeniusVersionInfo, + initialWorkspaceInfo, + useAppContext, + userProfilePlaceholder, +} from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' +import { defaultSystemFeatures } from '@/types/feature' import CustomPage from '../index' -// Mock external dependencies only vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(), })) - vi.mock('@/context/modal-context', () => ({ useModalContext: vi.fn(), })) - -// Mock the complex CustomWebAppBrand component to avoid dependency issues -// This is acceptable because it has complex dependencies (fetch, APIs) -vi.mock('@/app/components/custom/custom-web-app-brand', () => ({ - default: () =>
CustomWebAppBrand
, +vi.mock('@/context/app-context', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useAppContext: vi.fn(), + } +}) +vi.mock('@/context/global-public-context', () => ({ + useGlobalPublicStore: vi.fn(), +})) +vi.mock('@/app/components/base/toast/context', () => ({ + useToastContext: vi.fn(), })) +const mockUseProviderContext = vi.mocked(useProviderContext) +const mockUseModalContext = vi.mocked(useModalContext) +const mockUseAppContext = vi.mocked(useAppContext) +const mockUseGlobalPublicStore = vi.mocked(useGlobalPublicStore) +const mockUseToastContext = vi.mocked(useToastContext) + +const createProviderContext = ({ + enableBilling = false, + planType = Plan.professional, +}: { + enableBilling?: boolean + planType?: Plan +} = {}) => { + return createMockProviderContextValue({ + enableBilling, + plan: { + ...defaultPlan, + type: planType, + }, + }) +} + +const createAppContextValue = (): AppContextValue => ({ + userProfile: userProfilePlaceholder, + 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, + useSelector: vi.fn() as unknown as AppContextValue['useSelector'], + isLoadingCurrentWorkspace: false, + isValidatingCurrentWorkspace: false, +}) + +const createSystemFeatures = (): SystemFeatures => ({ + ...defaultSystemFeatures, + branding: { + ...defaultSystemFeatures.branding, + enabled: true, + workspace_logo: 'https://example.com/workspace-logo.png', + }, +}) + describe('CustomPage', () => { - const mockSetShowPricingModal = vi.fn() + const setShowPricingModal = vi.fn() beforeEach(() => { vi.clearAllMocks() - // Default mock setup - ;(useModalContext as Mock).mockReturnValue({ - setShowPricingModal: mockSetShowPricingModal, - }) + mockUseProviderContext.mockReturnValue(createProviderContext()) + mockUseModalContext.mockReturnValue({ + setShowPricingModal, + } as unknown as ReturnType) + mockUseAppContext.mockReturnValue(createAppContextValue()) + mockUseGlobalPublicStore.mockImplementation(selector => selector({ + systemFeatures: createSystemFeatures(), + setSystemFeatures: vi.fn(), + })) + mockUseToastContext.mockReturnValue({ + notify: vi.fn(), + } as unknown as ReturnType) }) - // Helper function to render with different provider contexts - const renderWithContext = (overrides = {}) => { - ;(useProviderContext as Mock).mockReturnValue( - createMockProviderContextValue(overrides), - ) - return render() - } - - // Rendering tests (REQUIRED) + // Integration coverage for the page and its child custom brand section. describe('Rendering', () => { - it('should render without crashing', () => { - // Arrange & Act - renderWithContext() + it('should render the custom brand configuration by default', () => { + render() - // Assert - expect(screen.getByTestId('custom-web-app-brand')).toBeInTheDocument() - }) - - it('should always render CustomWebAppBrand component', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert - expect(screen.getByTestId('custom-web-app-brand')).toBeInTheDocument() - }) - - it('should have correct layout structure', () => { - // Arrange & Act - const { container } = renderWithContext() - - // Assert - const mainContainer = container.querySelector('.flex.flex-col') - expect(mainContainer).toBeInTheDocument() - }) - }) - - // Conditional Rendering - Billing Tip - describe('Billing Tip Banner', () => { - it('should show billing tip when enableBilling is true and plan is sandbox', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert - expect(screen.getByText('custom.upgradeTip.title')).toBeInTheDocument() - expect(screen.getByText('custom.upgradeTip.des')).toBeInTheDocument() - expect(screen.getByText('billing.upgradeBtn.encourageShort')).toBeInTheDocument() - }) - - it('should not show billing tip when enableBilling is false', () => { - // Arrange & Act - renderWithContext({ - enableBilling: false, - plan: { type: Plan.sandbox }, - }) - - // Assert + expect(screen.getByText('custom.webapp.removeBrand')).toBeInTheDocument() + expect(screen.getByText('Chatflow App')).toBeInTheDocument() expect(screen.queryByText('custom.upgradeTip.title')).not.toBeInTheDocument() - expect(screen.queryByText('custom.upgradeTip.des')).not.toBeInTheDocument() - }) - - it('should not show billing tip when plan is professional', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.professional }, - }) - - // Assert - expect(screen.queryByText('custom.upgradeTip.title')).not.toBeInTheDocument() - expect(screen.queryByText('custom.upgradeTip.des')).not.toBeInTheDocument() - }) - - it('should not show billing tip when plan is team', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.team }, - }) - - // Assert - expect(screen.queryByText('custom.upgradeTip.title')).not.toBeInTheDocument() - expect(screen.queryByText('custom.upgradeTip.des')).not.toBeInTheDocument() - }) - - it('should have correct gradient styling for billing tip banner', () => { - // Arrange & Act - const { container } = renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert - const banner = container.querySelector('.bg-gradient-to-r') - expect(banner).toBeInTheDocument() - expect(banner).toHaveClass('from-components-input-border-active-prompt-1') - expect(banner).toHaveClass('to-components-input-border-active-prompt-2') - expect(banner).toHaveClass('p-4') - expect(banner).toHaveClass('pl-6') - expect(banner).toHaveClass('shadow-lg') - }) - }) - - // Conditional Rendering - Contact Sales - describe('Contact Sales Section', () => { - it('should show contact section when enableBilling is true and plan is professional', () => { - // Arrange & Act - const { container } = renderWithContext({ - enableBilling: true, - plan: { type: Plan.professional }, - }) - - // Assert - Check that contact section exists with all parts - const contactSection = container.querySelector('.absolute.bottom-0') - expect(contactSection).toBeInTheDocument() - expect(contactSection).toHaveTextContent('custom.customize.prefix') - expect(screen.getByText('custom.customize.contactUs')).toBeInTheDocument() - expect(contactSection).toHaveTextContent('custom.customize.suffix') - }) - - it('should show contact section when enableBilling is true and plan is team', () => { - // Arrange & Act - const { container } = renderWithContext({ - enableBilling: true, - plan: { type: Plan.team }, - }) - - // Assert - Check that contact section exists with all parts - const contactSection = container.querySelector('.absolute.bottom-0') - expect(contactSection).toBeInTheDocument() - expect(contactSection).toHaveTextContent('custom.customize.prefix') - expect(screen.getByText('custom.customize.contactUs')).toBeInTheDocument() - expect(contactSection).toHaveTextContent('custom.customize.suffix') - }) - - it('should not show contact section when enableBilling is false', () => { - // Arrange & Act - renderWithContext({ - enableBilling: false, - plan: { type: Plan.professional }, - }) - - // Assert - expect(screen.queryByText('custom.customize.prefix')).not.toBeInTheDocument() expect(screen.queryByText('custom.customize.contactUs')).not.toBeInTheDocument() }) - it('should not show contact section when plan is sandbox', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert - expect(screen.queryByText('custom.customize.prefix')).not.toBeInTheDocument() - expect(screen.queryByText('custom.customize.contactUs')).not.toBeInTheDocument() - }) - - it('should render contact link with correct URL', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.professional }, - }) - - // Assert - const link = screen.getByText('custom.customize.contactUs').closest('a') - expect(link).toHaveAttribute('href', contactSalesUrl) - expect(link).toHaveAttribute('target', '_blank') - expect(link).toHaveAttribute('rel', 'noopener noreferrer') - }) - - it('should have correct positioning for contact section', () => { - // Arrange & Act - const { container } = renderWithContext({ - enableBilling: true, - plan: { type: Plan.professional }, - }) - - // Assert - const contactSection = container.querySelector('.absolute.bottom-0') - expect(contactSection).toBeInTheDocument() - expect(contactSection).toHaveClass('h-[50px]') - expect(contactSection).toHaveClass('text-xs') - expect(contactSection).toHaveClass('leading-[50px]') - }) - }) - - // User Interactions - describe('User Interactions', () => { - it('should call setShowPricingModal when upgrade button is clicked', async () => { - // Arrange + it('should show the upgrade banner and open pricing modal for sandbox billing', async () => { const user = userEvent.setup() - renderWithContext({ + mockUseProviderContext.mockReturnValue(createProviderContext({ enableBilling: true, - plan: { type: Plan.sandbox }, - }) + planType: Plan.sandbox, + })) - // Act - const upgradeButton = screen.getByText('billing.upgradeBtn.encourageShort') - await user.click(upgradeButton) + render() - // Assert - expect(mockSetShowPricingModal).toHaveBeenCalledTimes(1) - }) - - it('should call setShowPricingModal without arguments', async () => { - // Arrange - const user = userEvent.setup() - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Act - const upgradeButton = screen.getByText('billing.upgradeBtn.encourageShort') - await user.click(upgradeButton) - - // Assert - expect(mockSetShowPricingModal).toHaveBeenCalledWith() - }) - - it('should handle multiple clicks on upgrade button', async () => { - // Arrange - const user = userEvent.setup() - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Act - const upgradeButton = screen.getByText('billing.upgradeBtn.encourageShort') - await user.click(upgradeButton) - await user.click(upgradeButton) - await user.click(upgradeButton) - - // Assert - expect(mockSetShowPricingModal).toHaveBeenCalledTimes(3) - }) - - it('should have correct button styling for upgrade button', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert - const upgradeButton = screen.getByText('billing.upgradeBtn.encourageShort') - expect(upgradeButton).toHaveClass('cursor-pointer') - expect(upgradeButton).toHaveClass('bg-white') - expect(upgradeButton).toHaveClass('text-text-accent') - expect(upgradeButton).toHaveClass('rounded-3xl') - }) - }) - - // Edge Cases (REQUIRED) - describe('Edge Cases', () => { - it('should handle undefined plan type gracefully', () => { - // Arrange & Act - expect(() => { - renderWithContext({ - enableBilling: true, - plan: { type: undefined }, - }) - }).not.toThrow() - - // Assert - expect(screen.getByTestId('custom-web-app-brand')).toBeInTheDocument() - }) - - it('should handle plan without type property', () => { - // Arrange & Act - expect(() => { - renderWithContext({ - enableBilling: true, - plan: { type: null }, - }) - }).not.toThrow() - - // Assert - expect(screen.getByTestId('custom-web-app-brand')).toBeInTheDocument() - }) - - it('should not show any banners when both conditions are false', () => { - // Arrange & Act - renderWithContext({ - enableBilling: false, - plan: { type: Plan.sandbox }, - }) - - // Assert - expect(screen.queryByText('custom.upgradeTip.title')).not.toBeInTheDocument() - expect(screen.queryByText('custom.customize.prefix')).not.toBeInTheDocument() - }) - - it('should handle enableBilling undefined', () => { - // Arrange & Act - expect(() => { - renderWithContext({ - enableBilling: undefined, - plan: { type: Plan.sandbox }, - }) - }).not.toThrow() - - // Assert - expect(screen.queryByText('custom.upgradeTip.title')).not.toBeInTheDocument() - }) - - it('should show only billing tip for sandbox plan, not contact section', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert expect(screen.getByText('custom.upgradeTip.title')).toBeInTheDocument() expect(screen.queryByText('custom.customize.contactUs')).not.toBeInTheDocument() + + await user.click(screen.getByText('billing.upgradeBtn.encourageShort')) + + expect(setShowPricingModal).toHaveBeenCalledTimes(1) }) - it('should show only contact section for professional plan, not billing tip', () => { - // Arrange & Act - renderWithContext({ + it('should show the contact link for professional workspaces', () => { + mockUseProviderContext.mockReturnValue(createProviderContext({ enableBilling: true, - plan: { type: Plan.professional }, - }) + planType: Plan.professional, + })) - // Assert + render() + + const contactLink = screen.getByText('custom.customize.contactUs').closest('a') expect(screen.queryByText('custom.upgradeTip.title')).not.toBeInTheDocument() - expect(screen.getByText('custom.customize.contactUs')).toBeInTheDocument() + expect(contactLink).toHaveAttribute('href', contactSalesUrl) + expect(contactLink).toHaveAttribute('target', '_blank') + expect(contactLink).toHaveAttribute('rel', 'noopener noreferrer') }) - it('should show only contact section for team plan, not billing tip', () => { - // Arrange & Act - renderWithContext({ + it('should show the contact link for team workspaces', () => { + mockUseProviderContext.mockReturnValue(createProviderContext({ enableBilling: true, - plan: { type: Plan.team }, - }) + planType: Plan.team, + })) - // Assert + render() + + expect(screen.getByText('custom.customize.contactUs')).toBeInTheDocument() expect(screen.queryByText('custom.upgradeTip.title')).not.toBeInTheDocument() - expect(screen.getByText('custom.customize.contactUs')).toBeInTheDocument() }) - it('should handle empty plan object', () => { - // Arrange & Act - expect(() => { - renderWithContext({ - enableBilling: true, - plan: {}, - }) - }).not.toThrow() - - // Assert - expect(screen.getByTestId('custom-web-app-brand')).toBeInTheDocument() - }) - }) - - // Accessibility Tests - describe('Accessibility', () => { - it('should have clickable upgrade button', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert - const upgradeButton = screen.getByText('billing.upgradeBtn.encourageShort') - expect(upgradeButton).toBeInTheDocument() - expect(upgradeButton).toHaveClass('cursor-pointer') - }) - - it('should have proper external link attributes on contact link', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.professional }, - }) - - // Assert - const link = screen.getByText('custom.customize.contactUs').closest('a') - expect(link).toHaveAttribute('rel', 'noopener noreferrer') - expect(link).toHaveAttribute('target', '_blank') - }) - - it('should have proper text hierarchy in billing tip', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert - const title = screen.getByText('custom.upgradeTip.title') - const description = screen.getByText('custom.upgradeTip.des') - - expect(title).toHaveClass('title-xl-semi-bold') - expect(description).toHaveClass('system-sm-regular') - }) - - it('should use semantic color classes', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert - Check that the billing tip has text content (which implies semantic colors) - expect(screen.getByText('custom.upgradeTip.title')).toBeInTheDocument() - }) - }) - - // Integration Tests - describe('Integration', () => { - it('should render both CustomWebAppBrand and billing tip together', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.sandbox }, - }) - - // Assert - expect(screen.getByTestId('custom-web-app-brand')).toBeInTheDocument() - expect(screen.getByText('custom.upgradeTip.title')).toBeInTheDocument() - }) - - it('should render both CustomWebAppBrand and contact section together', () => { - // Arrange & Act - renderWithContext({ - enableBilling: true, - plan: { type: Plan.professional }, - }) - - // Assert - expect(screen.getByTestId('custom-web-app-brand')).toBeInTheDocument() - expect(screen.getByText('custom.customize.contactUs')).toBeInTheDocument() - }) - - it('should render only CustomWebAppBrand when no billing conditions met', () => { - // Arrange & Act - renderWithContext({ + it('should hide both billing sections when billing is disabled', () => { + mockUseProviderContext.mockReturnValue(createProviderContext({ enableBilling: false, - plan: { type: Plan.sandbox }, - }) + planType: Plan.sandbox, + })) + + render() - // Assert - expect(screen.getByTestId('custom-web-app-brand')).toBeInTheDocument() expect(screen.queryByText('custom.upgradeTip.title')).not.toBeInTheDocument() expect(screen.queryByText('custom.customize.contactUs')).not.toBeInTheDocument() }) diff --git a/web/app/components/custom/custom-web-app-brand/__tests__/index.spec.tsx b/web/app/components/custom/custom-web-app-brand/__tests__/index.spec.tsx index 1d17a2ae0f..fd78377e6d 100644 --- a/web/app/components/custom/custom-web-app-brand/__tests__/index.spec.tsx +++ b/web/app/components/custom/custom-web-app-brand/__tests__/index.spec.tsx @@ -1,147 +1,158 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/image-uploader/utils' -import { useToastContext } from '@/app/components/base/toast/context' -import { Plan } from '@/app/components/billing/type' -import { useAppContext } from '@/context/app-context' -import { useGlobalPublicStore } from '@/context/global-public-context' -import { useProviderContext } from '@/context/provider-context' -import { updateCurrentWorkspace } from '@/service/common' +import useWebAppBrand from '../hooks/use-web-app-brand' import CustomWebAppBrand from '../index' -vi.mock('@/app/components/base/toast/context', () => ({ - useToastContext: vi.fn(), -})) -vi.mock('@/service/common', () => ({ - updateCurrentWorkspace: vi.fn(), -})) -vi.mock('@/context/app-context', () => ({ - useAppContext: vi.fn(), -})) -vi.mock('@/context/provider-context', () => ({ - useProviderContext: vi.fn(), -})) -vi.mock('@/context/global-public-context', () => ({ - useGlobalPublicStore: vi.fn(), -})) -vi.mock('@/app/components/base/image-uploader/utils', () => ({ - imageUpload: vi.fn(), - getImageUploadErrorMessage: vi.fn(), +vi.mock('../hooks/use-web-app-brand', () => ({ + default: vi.fn(), })) -const mockNotify = vi.fn() -const mockUseToastContext = vi.mocked(useToastContext) -const mockUpdateCurrentWorkspace = vi.mocked(updateCurrentWorkspace) -const mockUseAppContext = vi.mocked(useAppContext) -const mockUseProviderContext = vi.mocked(useProviderContext) -const mockUseGlobalPublicStore = vi.mocked(useGlobalPublicStore) -const mockImageUpload = vi.mocked(imageUpload) -const mockGetImageUploadErrorMessage = vi.mocked(getImageUploadErrorMessage) +const mockUseWebAppBrand = vi.mocked(useWebAppBrand) -const defaultPlanUsage = { - buildApps: 0, - teamMembers: 0, - annotatedResponse: 0, - documentsUploadQuota: 0, - apiRateLimit: 0, - triggerEvents: 0, - vectorSpace: 0, +const createHookState = (overrides: Partial> = {}): ReturnType => ({ + fileId: '', + imgKey: 100, + uploadProgress: 0, + uploading: false, + webappLogo: 'https://example.com/replace.png', + webappBrandRemoved: false, + uploadDisabled: false, + workspaceLogo: 'https://example.com/workspace-logo.png', + isSandbox: false, + isCurrentWorkspaceManager: true, + handleApply: vi.fn(), + handleCancel: vi.fn(), + handleChange: vi.fn(), + handleRestore: vi.fn(), + handleSwitch: vi.fn(), + ...overrides, +}) + +const renderComponent = (overrides: Partial> = {}) => { + const hookState = createHookState(overrides) + mockUseWebAppBrand.mockReturnValue(hookState) + return { + hookState, + ...render(), + } } -const renderComponent = () => render() - describe('CustomWebAppBrand', () => { beforeEach(() => { vi.clearAllMocks() - mockUseToastContext.mockReturnValue({ notify: mockNotify } as unknown as ReturnType) - mockUpdateCurrentWorkspace.mockResolvedValue({} as unknown as Awaited>) - mockUseAppContext.mockReturnValue({ - currentWorkspace: { - custom_config: { - replace_webapp_logo: 'https://example.com/replace.png', - remove_webapp_brand: false, - }, - }, - mutateCurrentWorkspace: vi.fn(), - isCurrentWorkspaceManager: true, - } as unknown as ReturnType) - mockUseProviderContext.mockReturnValue({ - plan: { - type: Plan.professional, - usage: defaultPlanUsage, - total: defaultPlanUsage, - reset: {}, - }, - enableBilling: false, - } as unknown as ReturnType) - const systemFeaturesState = { - branding: { - enabled: true, - workspace_logo: 'https://example.com/workspace-logo.png', - }, - } - mockUseGlobalPublicStore.mockImplementation(selector => selector ? selector({ systemFeatures: systemFeaturesState, setSystemFeatures: vi.fn() } as unknown as ReturnType) : { systemFeatures: systemFeaturesState }) - mockGetImageUploadErrorMessage.mockReturnValue('upload error') }) - it('disables upload controls when the user cannot manage the workspace', () => { - mockUseAppContext.mockReturnValue({ - currentWorkspace: { - custom_config: { - replace_webapp_logo: '', - remove_webapp_brand: false, - }, - }, - mutateCurrentWorkspace: vi.fn(), - isCurrentWorkspaceManager: false, - } as unknown as ReturnType) + // Integration coverage for the root component with the hook mocked at the boundary. + describe('Rendering', () => { + it('should render the upload controls and preview cards with restore action', () => { + renderComponent() - const { container } = renderComponent() - const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement - expect(fileInput).toBeDisabled() - }) - - it('toggles remove brand switch and calls the backend + mutate', async () => { - const mutateMock = vi.fn() - mockUseAppContext.mockReturnValue({ - currentWorkspace: { - custom_config: { - replace_webapp_logo: '', - remove_webapp_brand: false, - }, - }, - mutateCurrentWorkspace: mutateMock, - isCurrentWorkspaceManager: true, - } as unknown as ReturnType) - - renderComponent() - const switchInput = screen.getByRole('switch') - fireEvent.click(switchInput) - - await waitFor(() => expect(mockUpdateCurrentWorkspace).toHaveBeenCalledWith({ - url: '/workspaces/custom-config', - body: { remove_webapp_brand: true }, - })) - await waitFor(() => expect(mutateMock).toHaveBeenCalled()) - }) - - it('shows cancel/apply buttons after successful upload and cancels properly', async () => { - mockImageUpload.mockImplementation(({ onProgressCallback, onSuccessCallback }) => { - onProgressCallback(50) - onSuccessCallback({ id: 'new-logo' }) + expect(screen.getByText('custom.webapp.removeBrand')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'custom.restore' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'custom.change' })).toBeInTheDocument() + expect(screen.getByText('Chatflow App')).toBeInTheDocument() + expect(screen.getByText('Workflow App')).toBeInTheDocument() }) - const { container } = renderComponent() - const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement - const testFile = new File(['content'], 'logo.png', { type: 'image/png' }) - fireEvent.change(fileInput, { target: { files: [testFile] } }) + it('should hide the restore action when uploads are disabled or no logo is configured', () => { + renderComponent({ + uploadDisabled: true, + webappLogo: '', + }) - await waitFor(() => expect(mockImageUpload).toHaveBeenCalled()) - await waitFor(() => screen.getByRole('button', { name: 'custom.apply' })) + expect(screen.queryByRole('button', { name: 'custom.restore' })).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: 'custom.upload' })).toBeDisabled() + }) - const cancelButton = screen.getByRole('button', { name: 'common.operation.cancel' }) - fireEvent.click(cancelButton) + it('should show the uploading button and failure message when upload state requires it', () => { + renderComponent({ + uploading: true, + uploadProgress: -1, + }) - await waitFor(() => expect(screen.queryByRole('button', { name: 'custom.apply' })).toBeNull()) + expect(screen.getByRole('button', { name: 'custom.uploading' })).toBeDisabled() + expect(screen.getByText('custom.uploadedFail')).toBeInTheDocument() + }) + + it('should show apply and cancel actions when a new file is ready', () => { + renderComponent({ + fileId: 'new-logo', + }) + + expect(screen.getByRole('button', { name: 'custom.apply' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'common.operation.cancel' })).toBeInTheDocument() + }) + + it('should disable the switch when sandbox restrictions are active', () => { + renderComponent({ + isSandbox: true, + }) + + expect(screen.getByRole('switch')).toHaveAttribute('aria-disabled', 'true') + }) + + it('should default the switch to unchecked when brand removal state is missing', () => { + const { container } = renderComponent({ + webappBrandRemoved: undefined, + }) + + expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'false') + expect(container.querySelector('.opacity-30')).not.toBeInTheDocument() + }) + + it('should dim the upload row when brand removal is enabled', () => { + const { container } = renderComponent({ + webappBrandRemoved: true, + uploadDisabled: true, + }) + + expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'true') + expect(container.querySelector('.opacity-30')).toBeInTheDocument() + }) + }) + + // User interactions delegated to the hook callbacks. + describe('Interactions', () => { + it('should delegate switch changes to the hook handler', () => { + const { hookState } = renderComponent() + + fireEvent.click(screen.getByRole('switch')) + + expect(hookState.handleSwitch).toHaveBeenCalledWith(true) + }) + + it('should delegate file input changes and reset the native input value on click', () => { + const { container, hookState } = renderComponent() + const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement + const file = new File(['logo'], 'logo.png', { type: 'image/png' }) + + Object.defineProperty(fileInput, 'value', { + configurable: true, + value: 'stale-selection', + writable: true, + }) + + fireEvent.click(fileInput) + fireEvent.change(fileInput, { + target: { files: [file] }, + }) + + expect(fileInput.value).toBe('') + expect(hookState.handleChange).toHaveBeenCalledTimes(1) + }) + + it('should delegate restore, cancel, and apply actions to the hook handlers', () => { + const { hookState } = renderComponent({ + fileId: 'new-logo', + }) + + fireEvent.click(screen.getByRole('button', { name: 'custom.restore' })) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' })) + fireEvent.click(screen.getByRole('button', { name: 'custom.apply' })) + + expect(hookState.handleRestore).toHaveBeenCalledTimes(1) + expect(hookState.handleCancel).toHaveBeenCalledTimes(1) + expect(hookState.handleApply).toHaveBeenCalledTimes(1) + }) }) }) diff --git a/web/app/components/custom/custom-web-app-brand/components/__tests__/chat-preview-card.spec.tsx b/web/app/components/custom/custom-web-app-brand/components/__tests__/chat-preview-card.spec.tsx new file mode 100644 index 0000000000..6605e40831 --- /dev/null +++ b/web/app/components/custom/custom-web-app-brand/components/__tests__/chat-preview-card.spec.tsx @@ -0,0 +1,31 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import ChatPreviewCard from '../chat-preview-card' + +describe('ChatPreviewCard', () => { + it('should render the chat preview with the powered-by footer', () => { + render( + , + ) + + expect(screen.getByText('Chatflow App')).toBeInTheDocument() + expect(screen.getByText('Hello! How can I assist you today?')).toBeInTheDocument() + expect(screen.getByText('Talk to Dify')).toBeInTheDocument() + expect(screen.getByText('POWERED BY')).toBeInTheDocument() + }) + + it('should hide chat branding footer when brand removal is enabled', () => { + render( + , + ) + + expect(screen.queryByText('POWERED BY')).not.toBeInTheDocument() + }) +}) diff --git a/web/app/components/custom/custom-web-app-brand/components/__tests__/powered-by-brand.spec.tsx b/web/app/components/custom/custom-web-app-brand/components/__tests__/powered-by-brand.spec.tsx new file mode 100644 index 0000000000..d77c8ce15b --- /dev/null +++ b/web/app/components/custom/custom-web-app-brand/components/__tests__/powered-by-brand.spec.tsx @@ -0,0 +1,41 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import PoweredByBrand from '../powered-by-brand' + +describe('PoweredByBrand', () => { + it('should render the workspace logo when available', () => { + render( + , + ) + + expect(screen.getByText('POWERED BY')).toBeInTheDocument() + expect(screen.getByAltText('logo')).toHaveAttribute('src', 'https://example.com/workspace-logo.png') + }) + + it('should fall back to the custom web app logo when workspace branding is unavailable', () => { + render( + , + ) + + expect(screen.getByAltText('logo')).toHaveAttribute('src', 'https://example.com/custom-logo.png?hash=42') + }) + + it('should fall back to the Dify logo when no custom branding exists', () => { + render() + + expect(screen.getByAltText('Dify logo')).toBeInTheDocument() + }) + + it('should render nothing when branding is removed', () => { + const { container } = render() + + expect(container).toBeEmptyDOMElement() + }) +}) diff --git a/web/app/components/custom/custom-web-app-brand/components/__tests__/workflow-preview-card.spec.tsx b/web/app/components/custom/custom-web-app-brand/components/__tests__/workflow-preview-card.spec.tsx new file mode 100644 index 0000000000..d563c4f40b --- /dev/null +++ b/web/app/components/custom/custom-web-app-brand/components/__tests__/workflow-preview-card.spec.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import WorkflowPreviewCard from '../workflow-preview-card' + +describe('WorkflowPreviewCard', () => { + it('should render the workflow preview with execute action and branding footer', () => { + render( + , + ) + + expect(screen.getByText('Workflow App')).toBeInTheDocument() + expect(screen.getByText('RUN ONCE')).toBeInTheDocument() + expect(screen.getByText('RUN BATCH')).toBeInTheDocument() + expect(screen.getByRole('button', { name: /Execute/i })).toBeDisabled() + expect(screen.getByAltText('logo')).toHaveAttribute('src', 'https://example.com/workspace-logo.png') + }) + + it('should hide workflow branding footer when brand removal is enabled', () => { + render( + , + ) + + expect(screen.queryByText('POWERED BY')).not.toBeInTheDocument() + }) +}) diff --git a/web/app/components/custom/custom-web-app-brand/components/chat-preview-card.tsx b/web/app/components/custom/custom-web-app-brand/components/chat-preview-card.tsx new file mode 100644 index 0000000000..5700a04e41 --- /dev/null +++ b/web/app/components/custom/custom-web-app-brand/components/chat-preview-card.tsx @@ -0,0 +1,78 @@ +import Button from '@/app/components/base/button' +import { cn } from '@/utils/classnames' +import PoweredByBrand from './powered-by-brand' + +type ChatPreviewCardProps = { + webappBrandRemoved?: boolean + workspaceLogo?: string + webappLogo?: string + imgKey: number +} + +const ChatPreviewCard = ({ + webappBrandRemoved, + workspaceLogo, + webappLogo, + imgKey, +}: ChatPreviewCardProps) => { + return ( +
+
+
+
+ +
+
Chatflow App
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
Hello! How can I assist you today?
+ +
+
Talk to Dify
+
+
+
+ ) +} + +export default ChatPreviewCard diff --git a/web/app/components/custom/custom-web-app-brand/components/powered-by-brand.tsx b/web/app/components/custom/custom-web-app-brand/components/powered-by-brand.tsx new file mode 100644 index 0000000000..8a0feffbc4 --- /dev/null +++ b/web/app/components/custom/custom-web-app-brand/components/powered-by-brand.tsx @@ -0,0 +1,31 @@ +import DifyLogo from '@/app/components/base/logo/dify-logo' + +type PoweredByBrandProps = { + webappBrandRemoved?: boolean + workspaceLogo?: string + webappLogo?: string + imgKey: number +} + +const PoweredByBrand = ({ + webappBrandRemoved, + workspaceLogo, + webappLogo, + imgKey, +}: PoweredByBrandProps) => { + if (webappBrandRemoved) + return null + + const previewLogo = workspaceLogo || (webappLogo ? `${webappLogo}?hash=${imgKey}` : '') + + return ( + <> +
POWERED BY
+ {previewLogo + ? logo + : } + + ) +} + +export default PoweredByBrand diff --git a/web/app/components/custom/custom-web-app-brand/components/workflow-preview-card.tsx b/web/app/components/custom/custom-web-app-brand/components/workflow-preview-card.tsx new file mode 100644 index 0000000000..276f77ce71 --- /dev/null +++ b/web/app/components/custom/custom-web-app-brand/components/workflow-preview-card.tsx @@ -0,0 +1,64 @@ +import Button from '@/app/components/base/button' +import { cn } from '@/utils/classnames' +import PoweredByBrand from './powered-by-brand' + +type WorkflowPreviewCardProps = { + webappBrandRemoved?: boolean + workspaceLogo?: string + webappLogo?: string + imgKey: number +} + +const WorkflowPreviewCard = ({ + webappBrandRemoved, + workspaceLogo, + webappLogo, + imgKey, +}: WorkflowPreviewCardProps) => { + return ( +
+
+
+
+ +
+
Workflow App
+
+ +
+
+
+
RUN ONCE
+
RUN BATCH
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+ +
+
+ ) +} + +export default WorkflowPreviewCard 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 new file mode 100644 index 0000000000..bb19c5accc --- /dev/null +++ b/web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx @@ -0,0 +1,385 @@ +import type { ChangeEvent } from 'react' +import type { AppContextValue } from '@/context/app-context' +import type { SystemFeatures } from '@/types/feature' +import { act, renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { createMockProviderContextValue } from '@/__mocks__/provider-context' +import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/image-uploader/utils' +import { useToastContext } from '@/app/components/base/toast/context' +import { defaultPlan } from '@/app/components/billing/config' +import { Plan } from '@/app/components/billing/type' +import { + initialLangGeniusVersionInfo, + initialWorkspaceInfo, + useAppContext, + userProfilePlaceholder, +} from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useProviderContext } from '@/context/provider-context' +import { updateCurrentWorkspace } from '@/service/common' +import { defaultSystemFeatures } from '@/types/feature' +import useWebAppBrand from '../use-web-app-brand' + +vi.mock('@/app/components/base/toast/context', () => ({ + useToastContext: vi.fn(), +})) +vi.mock('@/service/common', () => ({ + updateCurrentWorkspace: vi.fn(), +})) +vi.mock('@/context/app-context', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useAppContext: vi.fn(), + } +}) +vi.mock('@/context/provider-context', () => ({ + useProviderContext: vi.fn(), +})) +vi.mock('@/context/global-public-context', () => ({ + useGlobalPublicStore: vi.fn(), +})) +vi.mock('@/app/components/base/image-uploader/utils', () => ({ + imageUpload: vi.fn(), + getImageUploadErrorMessage: vi.fn(), +})) + +const mockNotify = vi.fn() +const mockUseToastContext = vi.mocked(useToastContext) +const mockUpdateCurrentWorkspace = vi.mocked(updateCurrentWorkspace) +const mockUseAppContext = vi.mocked(useAppContext) +const mockUseProviderContext = vi.mocked(useProviderContext) +const mockUseGlobalPublicStore = vi.mocked(useGlobalPublicStore) +const mockImageUpload = vi.mocked(imageUpload) +const mockGetImageUploadErrorMessage = vi.mocked(getImageUploadErrorMessage) + +const createProviderContext = ({ + enableBilling = false, + planType = Plan.professional, +}: { + enableBilling?: boolean + planType?: Plan +} = {}) => { + return createMockProviderContextValue({ + enableBilling, + plan: { + ...defaultPlan, + type: planType, + }, + }) +} + +const createSystemFeatures = (brandingOverrides: Partial = {}): SystemFeatures => ({ + ...defaultSystemFeatures, + branding: { + ...defaultSystemFeatures.branding, + enabled: true, + workspace_logo: 'https://example.com/workspace-logo.png', + ...brandingOverrides, + }, +}) + +const createAppContextValue = (overrides: Partial = {}): AppContextValue => { + const { currentWorkspace: currentWorkspaceOverride, ...restOverrides } = overrides + const workspaceOverrides: Partial = currentWorkspaceOverride ?? {} + const currentWorkspace = { + ...initialWorkspaceInfo, + ...workspaceOverrides, + custom_config: { + replace_webapp_logo: 'https://example.com/replace.png', + remove_webapp_brand: false, + ...workspaceOverrides.custom_config, + }, + } + + return { + userProfile: userProfilePlaceholder, + mutateUserProfile: vi.fn(), + isCurrentWorkspaceManager: true, + isCurrentWorkspaceOwner: false, + isCurrentWorkspaceEditor: false, + isCurrentWorkspaceDatasetOperator: false, + mutateCurrentWorkspace: vi.fn(), + langGeniusVersionInfo: initialLangGeniusVersionInfo, + useSelector: vi.fn() as unknown as AppContextValue['useSelector'], + isLoadingCurrentWorkspace: false, + isValidatingCurrentWorkspace: false, + ...restOverrides, + currentWorkspace, + } +} + +describe('useWebAppBrand', () => { + let appContextValue: AppContextValue + let systemFeatures: SystemFeatures + + beforeEach(() => { + vi.clearAllMocks() + + appContextValue = createAppContextValue() + systemFeatures = createSystemFeatures() + + mockUseToastContext.mockReturnValue({ notify: mockNotify } as unknown as ReturnType) + mockUpdateCurrentWorkspace.mockResolvedValue(appContextValue.currentWorkspace) + mockUseAppContext.mockImplementation(() => appContextValue) + mockUseProviderContext.mockReturnValue(createProviderContext()) + mockUseGlobalPublicStore.mockImplementation(selector => selector({ + systemFeatures, + setSystemFeatures: vi.fn(), + })) + mockGetImageUploadErrorMessage.mockReturnValue('upload error') + }) + + // Derived state from context and store inputs. + describe('derived state', () => { + it('should expose workspace branding and upload availability by default', () => { + const { result } = renderHook(() => useWebAppBrand()) + + expect(result.current.webappLogo).toBe('https://example.com/replace.png') + expect(result.current.workspaceLogo).toBe('https://example.com/workspace-logo.png') + expect(result.current.uploadDisabled).toBe(false) + expect(result.current.uploading).toBe(false) + }) + + it('should disable uploads in sandbox workspaces and when branding is removed', () => { + mockUseProviderContext.mockReturnValue(createProviderContext({ + enableBilling: true, + planType: Plan.sandbox, + })) + appContextValue = createAppContextValue({ + currentWorkspace: { + ...initialWorkspaceInfo, + custom_config: { + replace_webapp_logo: 'https://example.com/replace.png', + remove_webapp_brand: true, + }, + }, + }) + + const { result } = renderHook(() => useWebAppBrand()) + + expect(result.current.isSandbox).toBe(true) + expect(result.current.webappBrandRemoved).toBe(true) + expect(result.current.uploadDisabled).toBe(true) + }) + + it('should fall back to an empty workspace logo when branding is disabled', () => { + systemFeatures = createSystemFeatures({ + enabled: false, + workspace_logo: '', + }) + + const { result } = renderHook(() => useWebAppBrand()) + + expect(result.current.workspaceLogo).toBe('') + }) + + it('should fall back to an empty custom logo when custom config is missing', () => { + appContextValue = { + ...createAppContextValue(), + currentWorkspace: { + ...initialWorkspaceInfo, + }, + } + + const { result } = renderHook(() => useWebAppBrand()) + + expect(result.current.webappLogo).toBe('') + }) + }) + + // State transitions driven by user actions. + describe('actions', () => { + it('should ignore empty file selections', () => { + const { result } = renderHook(() => useWebAppBrand()) + + act(() => { + result.current.handleChange({ + target: { files: [] }, + } as unknown as ChangeEvent) + }) + + expect(mockImageUpload).not.toHaveBeenCalled() + }) + + it('should reject oversized files before upload starts', () => { + const { result } = renderHook(() => useWebAppBrand()) + const oversizedFile = new File(['logo'], 'logo.png', { type: 'image/png' }) + + Object.defineProperty(oversizedFile, 'size', { + configurable: true, + value: 5 * 1024 * 1024 + 1, + }) + + act(() => { + result.current.handleChange({ + target: { files: [oversizedFile] }, + } as unknown as ChangeEvent) + }) + + expect(mockImageUpload).not.toHaveBeenCalled() + expect(mockNotify).toHaveBeenCalledWith({ + type: 'error', + message: 'common.imageUploader.uploadFromComputerLimit:{"size":5}', + }) + }) + + it('should update upload state after a successful file upload', () => { + mockImageUpload.mockImplementation(({ onProgressCallback, onSuccessCallback }) => { + onProgressCallback(100) + onSuccessCallback({ id: 'new-logo' }) + }) + + const { result } = renderHook(() => useWebAppBrand()) + + act(() => { + result.current.handleChange({ + target: { files: [new File(['logo'], 'logo.png', { type: 'image/png' })] }, + } as unknown as ChangeEvent) + }) + + expect(result.current.fileId).toBe('new-logo') + expect(result.current.uploadProgress).toBe(100) + expect(result.current.uploading).toBe(false) + }) + + it('should expose the uploading state while progress is incomplete', () => { + mockImageUpload.mockImplementation(({ onProgressCallback }) => { + onProgressCallback(50) + }) + + const { result } = renderHook(() => useWebAppBrand()) + + act(() => { + result.current.handleChange({ + target: { files: [new File(['logo'], 'logo.png', { type: 'image/png' })] }, + } as unknown as ChangeEvent) + }) + + expect(result.current.uploadProgress).toBe(50) + expect(result.current.uploading).toBe(true) + }) + + it('should surface upload errors and set the failure state', () => { + mockImageUpload.mockImplementation(({ onErrorCallback }) => { + onErrorCallback({ response: { code: 'forbidden' } }) + }) + + const { result } = renderHook(() => useWebAppBrand()) + + act(() => { + result.current.handleChange({ + target: { files: [new File(['logo'], 'logo.png', { type: 'image/png' })] }, + } as unknown as ChangeEvent) + }) + + expect(mockGetImageUploadErrorMessage).toHaveBeenCalled() + expect(mockNotify).toHaveBeenCalledWith({ + type: 'error', + message: 'upload error', + }) + expect(result.current.uploadProgress).toBe(-1) + }) + + it('should persist the selected logo and reset transient state on apply', async () => { + const mutateCurrentWorkspace = vi.fn() + appContextValue = createAppContextValue({ + mutateCurrentWorkspace, + }) + mockImageUpload.mockImplementation(({ onSuccessCallback }) => { + onSuccessCallback({ id: 'new-logo' }) + }) + + const { result } = renderHook(() => useWebAppBrand()) + + act(() => { + result.current.handleChange({ + target: { files: [new File(['logo'], 'logo.png', { type: 'image/png' })] }, + } as unknown as ChangeEvent) + }) + + const previousImgKey = result.current.imgKey + const dateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(previousImgKey + 1) + + await act(async () => { + await result.current.handleApply() + }) + + expect(mockUpdateCurrentWorkspace).toHaveBeenCalledWith({ + url: '/workspaces/custom-config', + body: { + remove_webapp_brand: false, + replace_webapp_logo: 'new-logo', + }, + }) + expect(mutateCurrentWorkspace).toHaveBeenCalledTimes(1) + expect(result.current.fileId).toBe('') + expect(result.current.imgKey).toBe(previousImgKey + 1) + dateNowSpy.mockRestore() + }) + + it('should restore the default branding configuration', async () => { + const mutateCurrentWorkspace = vi.fn() + appContextValue = createAppContextValue({ + mutateCurrentWorkspace, + }) + + const { result } = renderHook(() => useWebAppBrand()) + + await act(async () => { + await result.current.handleRestore() + }) + + expect(mockUpdateCurrentWorkspace).toHaveBeenCalledWith({ + url: '/workspaces/custom-config', + body: { + remove_webapp_brand: false, + replace_webapp_logo: '', + }, + }) + expect(mutateCurrentWorkspace).toHaveBeenCalledTimes(1) + }) + + it('should persist brand removal changes', async () => { + const mutateCurrentWorkspace = vi.fn() + appContextValue = createAppContextValue({ + mutateCurrentWorkspace, + }) + + const { result } = renderHook(() => useWebAppBrand()) + + await act(async () => { + await result.current.handleSwitch(true) + }) + + expect(mockUpdateCurrentWorkspace).toHaveBeenCalledWith({ + url: '/workspaces/custom-config', + body: { + remove_webapp_brand: true, + }, + }) + expect(mutateCurrentWorkspace).toHaveBeenCalledTimes(1) + }) + + it('should clear temporary upload state on cancel', () => { + mockImageUpload.mockImplementation(({ onSuccessCallback }) => { + onSuccessCallback({ id: 'new-logo' }) + }) + + const { result } = renderHook(() => useWebAppBrand()) + + act(() => { + result.current.handleChange({ + target: { files: [new File(['logo'], 'logo.png', { type: 'image/png' })] }, + } as unknown as ChangeEvent) + }) + + act(() => { + result.current.handleCancel() + }) + + expect(result.current.fileId).toBe('') + expect(result.current.uploadProgress).toBe(0) + }) + }) +}) diff --git a/web/app/components/custom/custom-web-app-brand/hooks/use-web-app-brand.ts b/web/app/components/custom/custom-web-app-brand/hooks/use-web-app-brand.ts new file mode 100644 index 0000000000..90ba0483c9 --- /dev/null +++ b/web/app/components/custom/custom-web-app-brand/hooks/use-web-app-brand.ts @@ -0,0 +1,121 @@ +import type { ChangeEvent } from 'react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/image-uploader/utils' +import { useToastContext } from '@/app/components/base/toast/context' +import { Plan } from '@/app/components/billing/type' +import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useProviderContext } from '@/context/provider-context' +import { updateCurrentWorkspace } from '@/service/common' + +const MAX_LOGO_FILE_SIZE = 5 * 1024 * 1024 +const CUSTOM_CONFIG_URL = '/workspaces/custom-config' +const WEB_APP_LOGO_UPLOAD_URL = '/workspaces/custom-config/webapp-logo/upload' + +const useWebAppBrand = () => { + const { t } = useTranslation() + const { notify } = useToastContext() + const { plan, enableBilling } = useProviderContext() + const { + currentWorkspace, + mutateCurrentWorkspace, + isCurrentWorkspaceManager, + } = useAppContext() + const [fileId, setFileId] = useState('') + const [imgKey, setImgKey] = useState(() => Date.now()) + const [uploadProgress, setUploadProgress] = useState(0) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) + + const isSandbox = enableBilling && plan.type === Plan.sandbox + const uploading = uploadProgress > 0 && uploadProgress < 100 + const webappLogo = currentWorkspace.custom_config?.replace_webapp_logo || '' + const webappBrandRemoved = currentWorkspace.custom_config?.remove_webapp_brand + const uploadDisabled = isSandbox || webappBrandRemoved || !isCurrentWorkspaceManager + const workspaceLogo = systemFeatures.branding.enabled ? systemFeatures.branding.workspace_logo : '' + + const persistWorkspaceBrand = async (body: Record) => { + await updateCurrentWorkspace({ + url: CUSTOM_CONFIG_URL, + body, + }) + mutateCurrentWorkspace() + } + + const handleChange = (e: ChangeEvent) => { + const file = e.target.files?.[0] + + if (!file) + return + + if (file.size > MAX_LOGO_FILE_SIZE) { + notify({ type: 'error', message: t('imageUploader.uploadFromComputerLimit', { ns: 'common', size: 5 }) }) + return + } + + imageUpload({ + file, + onProgressCallback: setUploadProgress, + onSuccessCallback: (res) => { + setUploadProgress(100) + setFileId(res.id) + }, + onErrorCallback: (error) => { + const errorMessage = getImageUploadErrorMessage( + error, + t('imageUploader.uploadFromComputerUploadError', { ns: 'common' }), + t, + ) + notify({ type: 'error', message: errorMessage }) + setUploadProgress(-1) + }, + }, false, WEB_APP_LOGO_UPLOAD_URL) + } + + const handleApply = async () => { + await persistWorkspaceBrand({ + remove_webapp_brand: webappBrandRemoved, + replace_webapp_logo: fileId, + }) + setFileId('') + setImgKey(Date.now()) + } + + const handleRestore = async () => { + await persistWorkspaceBrand({ + remove_webapp_brand: false, + replace_webapp_logo: '', + }) + } + + const handleSwitch = async (checked: boolean) => { + await persistWorkspaceBrand({ + remove_webapp_brand: checked, + }) + } + + const handleCancel = () => { + setFileId('') + setUploadProgress(0) + } + + return { + fileId, + imgKey, + uploadProgress, + uploading, + webappLogo, + webappBrandRemoved, + uploadDisabled, + workspaceLogo, + isSandbox, + isCurrentWorkspaceManager, + handleApply, + handleCancel, + handleChange, + handleRestore, + handleSwitch, + } +} + +export default useWebAppBrand diff --git a/web/app/components/custom/custom-web-app-brand/index.tsx b/web/app/components/custom/custom-web-app-brand/index.tsx index fa79c9540a..02a6419f18 100644 --- a/web/app/components/custom/custom-web-app-brand/index.tsx +++ b/web/app/components/custom/custom-web-app-brand/index.tsx @@ -1,118 +1,33 @@ -import type { ChangeEvent } from 'react' -import { - RiEditBoxLine, - RiEqualizer2Line, - RiExchange2Fill, - RiImageAddLine, - RiLayoutLeft2Line, - RiLoader2Line, - RiPlayLargeLine, -} from '@remixicon/react' -import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' -import { BubbleTextMod } from '@/app/components/base/icons/src/vender/solid/communication' -import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/image-uploader/utils' -import DifyLogo from '@/app/components/base/logo/dify-logo' import Switch from '@/app/components/base/switch' -import { useToastContext } from '@/app/components/base/toast/context' -import { Plan } from '@/app/components/billing/type' -import { useAppContext } from '@/context/app-context' -import { useGlobalPublicStore } from '@/context/global-public-context' -import { useProviderContext } from '@/context/provider-context' -import { - updateCurrentWorkspace, -} from '@/service/common' import { cn } from '@/utils/classnames' +import ChatPreviewCard from './components/chat-preview-card' +import WorkflowPreviewCard from './components/workflow-preview-card' +import useWebAppBrand from './hooks/use-web-app-brand' const ALLOW_FILE_EXTENSIONS = ['svg', 'png'] const CustomWebAppBrand = () => { const { t } = useTranslation() - const { notify } = useToastContext() - const { plan, enableBilling } = useProviderContext() const { - currentWorkspace, - mutateCurrentWorkspace, + fileId, + imgKey, + uploadProgress, + uploading, + webappLogo, + webappBrandRemoved, + uploadDisabled, + workspaceLogo, isCurrentWorkspaceManager, - } = useAppContext() - const [fileId, setFileId] = useState('') - const [imgKey, setImgKey] = useState(() => Date.now()) - const [uploadProgress, setUploadProgress] = useState(0) - const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) - const isSandbox = enableBilling && plan.type === Plan.sandbox - const uploading = uploadProgress > 0 && uploadProgress < 100 - const webappLogo = currentWorkspace.custom_config?.replace_webapp_logo || '' - const webappBrandRemoved = currentWorkspace.custom_config?.remove_webapp_brand - const uploadDisabled = isSandbox || webappBrandRemoved || !isCurrentWorkspaceManager - - const handleChange = (e: ChangeEvent) => { - const file = e.target.files?.[0] - - if (!file) - return - - if (file.size > 5 * 1024 * 1024) { - notify({ type: 'error', message: t('imageUploader.uploadFromComputerLimit', { ns: 'common', size: 5 }) }) - return - } - - imageUpload({ - file, - onProgressCallback: (progress) => { - setUploadProgress(progress) - }, - onSuccessCallback: (res) => { - setUploadProgress(100) - setFileId(res.id) - }, - onErrorCallback: (error?: any) => { - const errorMessage = getImageUploadErrorMessage(error, t('imageUploader.uploadFromComputerUploadError', { ns: 'common' }), t as any) - notify({ type: 'error', message: errorMessage }) - setUploadProgress(-1) - }, - }, false, '/workspaces/custom-config/webapp-logo/upload') - } - - const handleApply = async () => { - await updateCurrentWorkspace({ - url: '/workspaces/custom-config', - body: { - remove_webapp_brand: webappBrandRemoved, - replace_webapp_logo: fileId, - }, - }) - mutateCurrentWorkspace() - setFileId('') - setImgKey(Date.now()) - } - - const handleRestore = async () => { - await updateCurrentWorkspace({ - url: '/workspaces/custom-config', - body: { - remove_webapp_brand: false, - replace_webapp_logo: '', - }, - }) - mutateCurrentWorkspace() - } - - const handleSwitch = async (checked: boolean) => { - await updateCurrentWorkspace({ - url: '/workspaces/custom-config', - body: { - remove_webapp_brand: checked, - }, - }) - mutateCurrentWorkspace() - } - - const handleCancel = () => { - setFileId('') - setUploadProgress(0) - } + isSandbox, + handleApply, + handleCancel, + handleChange, + handleRestore, + handleSwitch, + } = useWebAppBrand() return (
@@ -149,7 +64,7 @@ const CustomWebAppBrand = () => { className="relative mr-2" disabled={uploadDisabled} > - + { (webappLogo || fileId) ? t('change', { ns: 'custom' }) @@ -172,7 +87,7 @@ const CustomWebAppBrand = () => { className="relative mr-2" disabled={true} > - + {t('uploading', { ns: 'custom' })} ) @@ -208,118 +123,18 @@ const CustomWebAppBrand = () => {
- {/* chat card */} -
-
-
-
- -
-
Chatflow App
-
- -
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- {!webappBrandRemoved && ( - <> -
POWERED BY
- { - systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo - ? logo - : webappLogo - ? logo - : - } - - )} -
-
-
-
-
-
-
Hello! How can I assist you today?
- -
-
Talk to Dify
-
-
-
- {/* workflow card */} -
-
-
-
- -
-
Workflow App
-
- -
-
-
-
RUN ONCE
-
RUN BATCH
-
-
-
-
-
-
-
-
-
-
- - -
-
-
- {!webappBrandRemoved && ( - <> -
POWERED BY
- { - systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo - ? logo - : webappLogo - ? logo - : - } - - )} -
-
+ +
) diff --git a/web/app/components/header/header-wrapper.spec.tsx b/web/app/components/header/__tests__/header-wrapper.spec.tsx similarity index 98% rename from web/app/components/header/header-wrapper.spec.tsx rename to web/app/components/header/__tests__/header-wrapper.spec.tsx index 80ddb14965..b1948e0992 100644 --- a/web/app/components/header/header-wrapper.spec.tsx +++ b/web/app/components/header/__tests__/header-wrapper.spec.tsx @@ -2,7 +2,7 @@ import { act, render, screen } from '@testing-library/react' import { usePathname } from 'next/navigation' import { vi } from 'vitest' import { useEventEmitterContextContext } from '@/context/event-emitter' -import HeaderWrapper from './header-wrapper' +import HeaderWrapper from '../header-wrapper' vi.mock('next/navigation', () => ({ usePathname: vi.fn(), diff --git a/web/app/components/header/index.spec.tsx b/web/app/components/header/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/index.spec.tsx rename to web/app/components/header/__tests__/index.spec.tsx index ea7fab8a8f..93ab7fb535 100644 --- a/web/app/components/header/index.spec.tsx +++ b/web/app/components/header/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import { vi } from 'vitest' -import Header from './index' +import Header from '../index' function createMockComponent(testId: string) { return () =>
diff --git a/web/app/components/header/ maintenance-notice.spec.tsx b/web/app/components/header/__tests__/maintenance-notice.spec.tsx similarity index 98% rename from web/app/components/header/ maintenance-notice.spec.tsx rename to web/app/components/header/__tests__/maintenance-notice.spec.tsx index 157b03eb17..82c66a05b8 100644 --- a/web/app/components/header/ maintenance-notice.spec.tsx +++ b/web/app/components/header/__tests__/maintenance-notice.spec.tsx @@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react' import { vi } from 'vitest' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import { NOTICE_I18N } from '@/i18n-config/language' -import MaintenanceNotice from './maintenance-notice' +import MaintenanceNotice from '../maintenance-notice' vi.mock('@/app/components/base/icons/src/vender/line/general', () => ({ X: ({ onClick }: { onClick?: () => void }) => diff --git a/web/app/components/header/account-setting/data-source-page/panel/index.spec.tsx b/web/app/components/header/account-setting/data-source-page/panel/__tests__/index.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/data-source-page/panel/index.spec.tsx rename to web/app/components/header/account-setting/data-source-page/panel/__tests__/index.spec.tsx index f03267bcba..d83cdb5360 100644 --- a/web/app/components/header/account-setting/data-source-page/panel/index.spec.tsx +++ b/web/app/components/header/account-setting/data-source-page/panel/__tests__/index.spec.tsx @@ -1,15 +1,15 @@ -import type { ConfigItemType } from './config-item' +import type { ConfigItemType } from '../config-item' import { fireEvent, render, screen } from '@testing-library/react' import { DataSourceProvider } from '@/models/common' -import Panel from './index' -import { DataSourceType } from './types' +import Panel from '../index' +import { DataSourceType } from '../types' /** * Panel Component Tests * Tests layout, conditional rendering, and interactions for data source panels (Notion and Website). */ -vi.mock('../data-source-notion/operate', () => ({ +vi.mock('../../data-source-notion/operate', () => ({ default: () =>
, })) diff --git a/web/app/components/header/account-setting/index.spec.tsx b/web/app/components/header/account-setting/index.spec.tsx deleted file mode 100644 index 3a98d8afb8..0000000000 --- a/web/app/components/header/account-setting/index.spec.tsx +++ /dev/null @@ -1,334 +0,0 @@ -import type { AppContextValue } from '@/context/app-context' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fireEvent, render, screen } from '@testing-library/react' -import { useAppContext } from '@/context/app-context' -import { baseProviderContextValue, useProviderContext } from '@/context/provider-context' -import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' -import { ACCOUNT_SETTING_TAB } from './constants' -import AccountSetting from './index' - -vi.mock('@/context/provider-context', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - useProviderContext: vi.fn(), - } -}) - -vi.mock('@/context/app-context', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - useAppContext: vi.fn(), - } -}) - -vi.mock('next/navigation', () => ({ - useRouter: vi.fn(() => ({ - push: vi.fn(), - replace: vi.fn(), - prefetch: vi.fn(), - })), - usePathname: vi.fn(() => '/'), - useParams: vi.fn(() => ({})), - useSearchParams: vi.fn(() => ({ get: vi.fn() })), -})) - -vi.mock('@/hooks/use-breakpoints', () => ({ - MediaType: { - mobile: 'mobile', - tablet: 'tablet', - pc: 'pc', - }, - default: vi.fn(), -})) - -vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ - useDefaultModel: vi.fn(() => ({ data: null, isLoading: false })), - useUpdateDefaultModel: vi.fn(() => ({ trigger: vi.fn() })), - useUpdateModelList: vi.fn(() => vi.fn()), - useModelList: vi.fn(() => ({ data: [], isLoading: false })), - useSystemDefaultModelAndModelList: vi.fn(() => [null, vi.fn()]), -})) - -vi.mock('@/service/use-datasource', () => ({ - useGetDataSourceListAuth: vi.fn(() => ({ data: { result: [] } })), -})) - -vi.mock('@/service/use-common', () => ({ - useApiBasedExtensions: vi.fn(() => ({ data: [], isPending: false })), - useMembers: vi.fn(() => ({ data: { accounts: [] }, refetch: vi.fn() })), - useProviderContext: vi.fn(), -})) - -const baseAppContextValue: AppContextValue = { - userProfile: { - id: '1', - name: 'Test User', - email: 'test@example.com', - avatar: '', - avatar_url: '', - is_password_set: false, - }, - mutateUserProfile: vi.fn(), - currentWorkspace: { - id: '1', - name: 'Workspace', - plan: '', - status: '', - created_at: 0, - role: 'owner', - providers: [], - trial_credits: 0, - trial_credits_used: 0, - next_credit_reset_date: 0, - }, - isCurrentWorkspaceManager: true, - isCurrentWorkspaceOwner: true, - isCurrentWorkspaceEditor: true, - isCurrentWorkspaceDatasetOperator: false, - mutateCurrentWorkspace: vi.fn(), - langGeniusVersionInfo: { - current_env: 'testing', - current_version: '0.1.0', - latest_version: '0.1.0', - release_date: '', - release_notes: '', - version: '0.1.0', - can_auto_update: false, - }, - useSelector: vi.fn(), - isLoadingCurrentWorkspace: false, - isValidatingCurrentWorkspace: false, -} - -describe('AccountSetting', () => { - const mockOnCancel = vi.fn() - const mockOnTabChange = vi.fn() - - beforeEach(() => { - vi.clearAllMocks() - vi.mocked(useProviderContext).mockReturnValue({ - ...baseProviderContextValue, - enableBilling: true, - enableReplaceWebAppLogo: true, - }) - vi.mocked(useAppContext).mockReturnValue(baseAppContextValue) - vi.mocked(useBreakpoints).mockReturnValue(MediaType.pc) - }) - - describe('Rendering', () => { - it('should render the sidebar with correct menu items', () => { - // Act - render( - - - , - ) - - // Assert - expect(screen.getByText('common.userProfile.settings')).toBeInTheDocument() - expect(screen.getByText('common.settings.provider')).toBeInTheDocument() - expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(0) - expect(screen.getByText('common.settings.billing')).toBeInTheDocument() - expect(screen.getByText('common.settings.dataSource')).toBeInTheDocument() - expect(screen.getByText('common.settings.apiBasedExtension')).toBeInTheDocument() - expect(screen.getByText('custom.custom')).toBeInTheDocument() - expect(screen.getAllByText('common.settings.language').length).toBeGreaterThan(0) - }) - - it('should respect the activeTab prop', () => { - // Act - render( - - - , - ) - - // Assert - // Check that the active item title is Data Source - const titles = screen.getAllByText('common.settings.dataSource') - // One in sidebar, one in header. - expect(titles.length).toBeGreaterThan(1) - }) - - it('should hide sidebar labels on mobile', () => { - // Arrange - vi.mocked(useBreakpoints).mockReturnValue(MediaType.mobile) - - // Act - render( - - - , - ) - - // Assert - // On mobile, the labels should not be rendered as per the implementation - expect(screen.queryByText('common.settings.provider')).not.toBeInTheDocument() - }) - - it('should filter items for dataset operator', () => { - // Arrange - vi.mocked(useAppContext).mockReturnValue({ - ...baseAppContextValue, - isCurrentWorkspaceDatasetOperator: true, - }) - - // Act - render( - - - , - ) - - // Assert - expect(screen.queryByText('common.settings.provider')).not.toBeInTheDocument() - expect(screen.queryByText('common.settings.members')).not.toBeInTheDocument() - expect(screen.getByText('common.settings.language')).toBeInTheDocument() - }) - - it('should hide billing and custom tabs when disabled', () => { - // Arrange - vi.mocked(useProviderContext).mockReturnValue({ - ...baseProviderContextValue, - enableBilling: false, - enableReplaceWebAppLogo: false, - }) - - // Act - render( - - - , - ) - - // Assert - expect(screen.queryByText('common.settings.billing')).not.toBeInTheDocument() - expect(screen.queryByText('custom.custom')).not.toBeInTheDocument() - }) - }) - - describe('Tab Navigation', () => { - it('should change active tab when clicking on menu item', () => { - // Arrange - render( - - - , - ) - - // Act - fireEvent.click(screen.getByText('common.settings.provider')) - - // Assert - expect(mockOnTabChange).toHaveBeenCalledWith(ACCOUNT_SETTING_TAB.PROVIDER) - // Check for content from ModelProviderPage - expect(screen.getByText('common.modelProvider.models')).toBeInTheDocument() - }) - - it('should navigate through various tabs and show correct details', () => { - // Act & Assert - render( - - - , - ) - - // Billing - fireEvent.click(screen.getByText('common.settings.billing')) - // Billing Page renders plansCommon.plan if data is loaded, or generic text. - // Checking for title in header which is always there - expect(screen.getAllByText('common.settings.billing').length).toBeGreaterThan(1) - - // Data Source - fireEvent.click(screen.getByText('common.settings.dataSource')) - expect(screen.getAllByText('common.settings.dataSource').length).toBeGreaterThan(1) - - // API Based Extension - fireEvent.click(screen.getByText('common.settings.apiBasedExtension')) - expect(screen.getAllByText('common.settings.apiBasedExtension').length).toBeGreaterThan(1) - - // Custom - fireEvent.click(screen.getByText('custom.custom')) - // Custom Page uses 'custom.custom' key as well. - expect(screen.getAllByText('custom.custom').length).toBeGreaterThan(1) - - // Language - fireEvent.click(screen.getAllByText('common.settings.language')[0]) - expect(screen.getAllByText('common.settings.language').length).toBeGreaterThan(1) - - // Members - fireEvent.click(screen.getAllByText('common.settings.members')[0]) - expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(1) - }) - }) - - describe('Interactions', () => { - it('should call onCancel when clicking close button', () => { - // Act - render( - - - , - ) - const buttons = screen.getAllByRole('button') - fireEvent.click(buttons[0]) - - // Assert - expect(mockOnCancel).toHaveBeenCalled() - }) - - it('should call onCancel when pressing Escape key', () => { - // Act - render( - - - , - ) - fireEvent.keyDown(document, { key: 'Escape' }) - - // Assert - expect(mockOnCancel).toHaveBeenCalled() - }) - - it('should update search value in provider tab', () => { - // Arrange - render( - - - , - ) - fireEvent.click(screen.getByText('common.settings.provider')) - - // Act - const input = screen.getByRole('textbox') - fireEvent.change(input, { target: { value: 'test-search' } }) - - // Assert - expect(input).toHaveValue('test-search') - expect(screen.getByText('common.modelProvider.models')).toBeInTheDocument() - }) - - it('should handle scroll event in panel', () => { - // Act - render( - - - , - ) - const scrollContainer = screen.getByRole('dialog').querySelector('.overflow-y-auto') - - // Assert - expect(scrollContainer).toBeInTheDocument() - if (scrollContainer) { - // Scroll down - fireEvent.scroll(scrollContainer, { target: { scrollTop: 100 } }) - expect(scrollContainer).toHaveClass('overflow-y-auto') - - // Scroll back up - fireEvent.scroll(scrollContainer, { target: { scrollTop: 0 } }) - } - }) - }) -}) diff --git a/web/app/components/header/account-setting/key-validator/KeyInput.spec.tsx b/web/app/components/header/account-setting/key-validator/__tests__/KeyInput.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/key-validator/KeyInput.spec.tsx rename to web/app/components/header/account-setting/key-validator/__tests__/KeyInput.spec.tsx index 60aafd1813..05df87f058 100644 --- a/web/app/components/header/account-setting/key-validator/KeyInput.spec.tsx +++ b/web/app/components/header/account-setting/key-validator/__tests__/KeyInput.spec.tsx @@ -1,8 +1,8 @@ import type { ComponentProps } from 'react' import { fireEvent, render, screen } from '@testing-library/react' import { useState } from 'react' -import { ValidatedStatus } from './declarations' -import KeyInput from './KeyInput' +import { ValidatedStatus } from '../declarations' +import KeyInput from '../KeyInput' type Props = ComponentProps diff --git a/web/app/components/header/account-setting/key-validator/Operate.spec.tsx b/web/app/components/header/account-setting/key-validator/__tests__/Operate.spec.tsx similarity index 98% rename from web/app/components/header/account-setting/key-validator/Operate.spec.tsx rename to web/app/components/header/account-setting/key-validator/__tests__/Operate.spec.tsx index 001f6727dc..6e8359fb72 100644 --- a/web/app/components/header/account-setting/key-validator/Operate.spec.tsx +++ b/web/app/components/header/account-setting/key-validator/__tests__/Operate.spec.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import Operate from './Operate' +import Operate from '../Operate' describe('Operate', () => { it('should render cancel and save when editing is open', () => { diff --git a/web/app/components/header/account-setting/key-validator/ValidateStatus.spec.tsx b/web/app/components/header/account-setting/key-validator/__tests__/ValidateStatus.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/key-validator/ValidateStatus.spec.tsx rename to web/app/components/header/account-setting/key-validator/__tests__/ValidateStatus.spec.tsx index 78ff6b06e1..ab4353f3cc 100644 --- a/web/app/components/header/account-setting/key-validator/ValidateStatus.spec.tsx +++ b/web/app/components/header/account-setting/key-validator/__tests__/ValidateStatus.spec.tsx @@ -4,7 +4,7 @@ import { ValidatedErrorMessage, ValidatedSuccessIcon, ValidatingTip, -} from './ValidateStatus' +} from '../ValidateStatus' describe('ValidateStatus', () => { beforeEach(() => { diff --git a/web/app/components/header/account-setting/key-validator/declarations.spec.ts b/web/app/components/header/account-setting/key-validator/__tests__/declarations.spec.ts similarity index 87% rename from web/app/components/header/account-setting/key-validator/declarations.spec.ts rename to web/app/components/header/account-setting/key-validator/__tests__/declarations.spec.ts index c7621ff265..348401084c 100644 --- a/web/app/components/header/account-setting/key-validator/declarations.spec.ts +++ b/web/app/components/header/account-setting/key-validator/__tests__/declarations.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { ValidatedStatus } from './declarations' +import { ValidatedStatus } from '../declarations' describe('declarations', () => { describe('ValidatedStatus', () => { diff --git a/web/app/components/header/account-setting/key-validator/hooks.spec.ts b/web/app/components/header/account-setting/key-validator/__tests__/hooks.spec.ts similarity index 95% rename from web/app/components/header/account-setting/key-validator/hooks.spec.ts rename to web/app/components/header/account-setting/key-validator/__tests__/hooks.spec.ts index 1beddf02f0..b84c5cf779 100644 --- a/web/app/components/header/account-setting/key-validator/hooks.spec.ts +++ b/web/app/components/header/account-setting/key-validator/__tests__/hooks.spec.ts @@ -1,6 +1,6 @@ import { act, renderHook } from '@testing-library/react' -import { ValidatedStatus } from './declarations' -import { useValidate } from './hooks' +import { ValidatedStatus } from '../declarations' +import { useValidate } from '../hooks' describe('useValidate', () => { beforeEach(() => { diff --git a/web/app/components/header/account-setting/key-validator/index.spec.tsx b/web/app/components/header/account-setting/key-validator/__tests__/index.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/key-validator/index.spec.tsx rename to web/app/components/header/account-setting/key-validator/__tests__/index.spec.tsx index 740b21169c..3d7a761329 100644 --- a/web/app/components/header/account-setting/key-validator/index.spec.tsx +++ b/web/app/components/header/account-setting/key-validator/__tests__/index.spec.tsx @@ -1,7 +1,7 @@ import type { ComponentProps } from 'react' -import type { Form } from './declarations' +import type { Form } from '../declarations' import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' -import KeyValidator from './index' +import KeyValidator from '../index' let subscriptionCallback: ((value: string) => void) | null = null const mockEmit = vi.fn((value: string) => { @@ -22,7 +22,7 @@ vi.mock('@/context/event-emitter', () => ({ const mockValidate = vi.fn() const mockUseValidate = vi.fn() -vi.mock('./hooks', () => ({ +vi.mock('../hooks', () => ({ useValidate: (...args: unknown[]) => mockUseValidate(...args), })) diff --git a/web/app/components/header/account-setting/language-page/index.spec.tsx b/web/app/components/header/account-setting/language-page/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/account-setting/language-page/index.spec.tsx rename to web/app/components/header/account-setting/language-page/__tests__/index.spec.tsx index 1748987570..fb032ebd62 100644 --- a/web/app/components/header/account-setting/language-page/index.spec.tsx +++ b/web/app/components/header/account-setting/language-page/__tests__/index.spec.tsx @@ -4,7 +4,7 @@ import { ToastProvider } from '@/app/components/base/toast' import { languages } from '@/i18n-config/language' import { updateUserProfile } from '@/service/common' import { timezones } from '@/utils/timezone' -import LanguagePage from './index' +import LanguagePage from '../index' const mockRefresh = vi.fn() const mockMutateUserProfile = vi.fn() diff --git a/web/app/components/header/account-setting/members-page/index.spec.tsx b/web/app/components/header/account-setting/members-page/__tests__/index.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/members-page/index.spec.tsx rename to web/app/components/header/account-setting/members-page/__tests__/index.spec.tsx index 5db1f7ae52..9513eeee02 100644 --- a/web/app/components/header/account-setting/members-page/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/__tests__/index.spec.tsx @@ -10,7 +10,7 @@ import { useGlobalPublicStore } from '@/context/global-public-context' import { useProviderContext } from '@/context/provider-context' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { useMembers } from '@/service/use-common' -import MembersPage from './index' +import MembersPage from '../index' vi.mock('@/context/app-context') vi.mock('@/context/global-public-context') @@ -18,7 +18,7 @@ vi.mock('@/context/provider-context') vi.mock('@/hooks/use-format-time-from-now') vi.mock('@/service/use-common') -vi.mock('./edit-workspace-modal', () => ({ +vi.mock('../edit-workspace-modal', () => ({ default: ({ onCancel }: { onCancel: () => void }) => (
Edit Workspace Modal
@@ -26,12 +26,12 @@ vi.mock('./edit-workspace-modal', () => ({
), })) -vi.mock('./invite-button', () => ({ +vi.mock('../invite-button', () => ({ default: ({ onClick, disabled }: { onClick: () => void, disabled: boolean }) => ( ), })) -vi.mock('./invite-modal', () => ({ +vi.mock('../invite-modal', () => ({ default: ({ onCancel, onSend }: { onCancel: () => void, onSend: (results: Array<{ email: string, status: 'success', url: string }>) => void }) => (
Invite Modal
@@ -40,7 +40,7 @@ vi.mock('./invite-modal', () => ({
), })) -vi.mock('./invited-modal', () => ({ +vi.mock('../invited-modal', () => ({ default: ({ onCancel }: { onCancel: () => void }) => (
Invited Modal
@@ -48,13 +48,13 @@ vi.mock('./invited-modal', () => ({
), })) -vi.mock('./operation', () => ({ +vi.mock('../operation', () => ({ default: () =>
Member Operation
, })) -vi.mock('./operation/transfer-ownership', () => ({ +vi.mock('../operation/transfer-ownership', () => ({ default: ({ onOperate }: { onOperate: () => void }) => , })) -vi.mock('./transfer-ownership-modal', () => ({ +vi.mock('../transfer-ownership-modal', () => ({ default: ({ onClose }: { onClose: () => void }) => (
Transfer Ownership Modal
diff --git a/web/app/components/header/account-setting/members-page/invite-button.spec.tsx b/web/app/components/header/account-setting/members-page/__tests__/invite-button.spec.tsx similarity index 98% rename from web/app/components/header/account-setting/members-page/invite-button.spec.tsx rename to web/app/components/header/account-setting/members-page/__tests__/invite-button.spec.tsx index 7388c7ef3b..c64e465279 100644 --- a/web/app/components/header/account-setting/members-page/invite-button.spec.tsx +++ b/web/app/components/header/account-setting/members-page/__tests__/invite-button.spec.tsx @@ -5,7 +5,7 @@ import { vi } from 'vitest' import { useAppContext } from '@/context/app-context' import { useGlobalPublicStore } from '@/context/global-public-context' import { useWorkspacePermissions } from '@/service/use-workspace' -import InviteButton from './invite-button' +import InviteButton from '../invite-button' vi.mock('@/context/app-context') vi.mock('@/context/global-public-context') diff --git a/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.spec.tsx b/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/account-setting/members-page/edit-workspace-modal/index.spec.tsx rename to web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx index ae0dd8cd4d..794aad9e0e 100644 --- a/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx @@ -6,7 +6,7 @@ import { vi } from 'vitest' import { ToastContext } from '@/app/components/base/toast/context' import { useAppContext } from '@/context/app-context' import { updateWorkspaceInfo } from '@/service/common' -import EditWorkspaceModal from './index' +import EditWorkspaceModal from '../index' vi.mock('@/context/app-context') vi.mock('@/service/common') diff --git a/web/app/components/header/account-setting/members-page/invite-modal/index.spec.tsx b/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/account-setting/members-page/invite-modal/index.spec.tsx rename to web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx index 04f5491cc8..d2aeca1b6c 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx @@ -5,7 +5,7 @@ import { vi } from 'vitest' import { ToastContext } from '@/app/components/base/toast/context' import { useProviderContextSelector } from '@/context/provider-context' import { inviteMember } from '@/service/common' -import InviteModal from './index' +import InviteModal from '../index' vi.mock('@/context/provider-context', () => ({ useProviderContextSelector: vi.fn(), diff --git a/web/app/components/header/account-setting/members-page/invite-modal/role-selector.spec.tsx b/web/app/components/header/account-setting/members-page/invite-modal/__tests__/role-selector.spec.tsx similarity index 98% rename from web/app/components/header/account-setting/members-page/invite-modal/role-selector.spec.tsx rename to web/app/components/header/account-setting/members-page/invite-modal/__tests__/role-selector.spec.tsx index f6cb43deed..af2c64179b 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/role-selector.spec.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/__tests__/role-selector.spec.tsx @@ -4,7 +4,7 @@ import { useState } from 'react' import { vi } from 'vitest' import { createMockProviderContextValue } from '@/__mocks__/provider-context' import { useProviderContext } from '@/context/provider-context' -import RoleSelector from './role-selector' +import RoleSelector from '../role-selector' vi.mock('@/context/provider-context') diff --git a/web/app/components/header/account-setting/members-page/invited-modal/index.spec.tsx b/web/app/components/header/account-setting/members-page/invited-modal/__tests__/index.spec.tsx similarity index 98% rename from web/app/components/header/account-setting/members-page/invited-modal/index.spec.tsx rename to web/app/components/header/account-setting/members-page/invited-modal/__tests__/index.spec.tsx index b67fc3e42c..32d0bdda50 100644 --- a/web/app/components/header/account-setting/members-page/invited-modal/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/invited-modal/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ import type { InvitationResult } from '@/models/common' import { render, screen } from '@testing-library/react' -import InvitedModal from './index' +import InvitedModal from '../index' const mockConfigState = vi.hoisted(() => ({ isCeEdition: true })) diff --git a/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.spec.tsx b/web/app/components/header/account-setting/members-page/invited-modal/__tests__/invitation-link.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/members-page/invited-modal/invitation-link.spec.tsx rename to web/app/components/header/account-setting/members-page/invited-modal/__tests__/invitation-link.spec.tsx index 1f8565e138..06761da8cb 100644 --- a/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.spec.tsx +++ b/web/app/components/header/account-setting/members-page/invited-modal/__tests__/invitation-link.spec.tsx @@ -1,7 +1,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import copy from 'copy-to-clipboard' -import InvitationLink from './invitation-link' +import InvitationLink from '../invitation-link' vi.mock('copy-to-clipboard') diff --git a/web/app/components/header/account-setting/members-page/operation/index.spec.tsx b/web/app/components/header/account-setting/members-page/operation/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/account-setting/members-page/operation/index.spec.tsx rename to web/app/components/header/account-setting/members-page/operation/__tests__/index.spec.tsx index cfa29ec083..53acda9576 100644 --- a/web/app/components/header/account-setting/members-page/operation/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/operation/__tests__/index.spec.tsx @@ -3,7 +3,7 @@ import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { vi } from 'vitest' import { ToastContext } from '@/app/components/base/toast/context' -import Operation from './index' +import Operation from '../index' const mockUpdateMemberRole = vi.fn() const mockDeleteMemberOrCancelInvitation = vi.fn() diff --git a/web/app/components/header/account-setting/members-page/operation/transfer-ownership.spec.tsx b/web/app/components/header/account-setting/members-page/operation/__tests__/transfer-ownership.spec.tsx similarity index 98% rename from web/app/components/header/account-setting/members-page/operation/transfer-ownership.spec.tsx rename to web/app/components/header/account-setting/members-page/operation/__tests__/transfer-ownership.spec.tsx index 74f86d601d..a38e66a2f8 100644 --- a/web/app/components/header/account-setting/members-page/operation/transfer-ownership.spec.tsx +++ b/web/app/components/header/account-setting/members-page/operation/__tests__/transfer-ownership.spec.tsx @@ -6,7 +6,7 @@ import { vi } from 'vitest' import { useAppContext } from '@/context/app-context' import { useGlobalPublicStore } from '@/context/global-public-context' import { useWorkspacePermissions } from '@/service/use-workspace' -import TransferOwnership from './transfer-ownership' +import TransferOwnership from '../transfer-ownership' vi.mock('@/context/app-context') vi.mock('@/context/global-public-context') diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.spec.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.spec.tsx rename to web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx index f57496451a..ea20e5719e 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/index.spec.tsx @@ -7,13 +7,13 @@ import { ToastContext } from '@/app/components/base/toast/context' import { useAppContext } from '@/context/app-context' import { ownershipTransfer, sendOwnerEmail, verifyOwnerEmail } from '@/service/common' import { useMembers } from '@/service/use-common' -import TransferOwnershipModal from './index' +import TransferOwnershipModal from '../index' vi.mock('@/context/app-context') vi.mock('@/service/common') vi.mock('@/service/use-common') -vi.mock('./member-selector', () => ({ +vi.mock('../member-selector', () => ({ default: ({ onSelect }: { onSelect: (id: string) => void }) => ( ), diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.spec.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/member-selector.spec.tsx similarity index 99% rename from web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.spec.tsx rename to web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/member-selector.spec.tsx index 4e38f5ecc2..5acca612ad 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.spec.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/__tests__/member-selector.spec.tsx @@ -2,7 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { vi } from 'vitest' import { useMembers } from '@/service/use-common' -import MemberSelector from './member-selector' +import MemberSelector from '../member-selector' vi.mock('@/service/use-common') diff --git a/web/app/components/header/account-setting/model-provider-page/hooks.spec.ts b/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts similarity index 99% rename from web/app/components/header/account-setting/model-provider-page/hooks.spec.ts rename to web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts index a202470f65..0fe48ad325 100644 --- a/web/app/components/header/account-setting/model-provider-page/hooks.spec.ts +++ b/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts @@ -6,7 +6,7 @@ import type { DefaultModelResponse, Model, ModelProvider, -} from './declarations' +} from '../declarations' import { act, renderHook, waitFor } from '@testing-library/react' import { useLocale } from '@/context/i18n' import { fetchDefaultModal, fetchModelList, fetchModelProviderCredentials } from '@/service/common' @@ -18,7 +18,7 @@ import { ModelStatusEnum, ModelTypeEnum, PreferredProviderTypeEnum, -} from './declarations' +} from '../declarations' import { useAnthropicBuyQuota, useCurrentProviderAndModel, @@ -35,8 +35,8 @@ import { useTextGenerationCurrentProviderAndModelAndModelList, useUpdateModelList, useUpdateModelProviders, -} from './hooks' -import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card' +} from '../hooks' +import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from '../provider-added-card' // Mock dependencies vi.mock('@tanstack/react-query', () => ({ diff --git a/web/app/components/header/account-setting/model-provider-page/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/model-provider-page/index.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx index 3f54864ff4..832bd77ec2 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx @@ -4,8 +4,8 @@ import { CurrentSystemQuotaTypeEnum, CustomConfigurationStatusEnum, QuotaUnitEnum, -} from './declarations' -import ModelProviderPage from './index' +} from '../declarations' +import ModelProviderPage from '../index' vi.mock('@/context/app-context', () => ({ useAppContext: () => ({ @@ -73,23 +73,23 @@ const mockDefaultModelState: { isLoading: false, } -vi.mock('./hooks', () => ({ +vi.mock('../hooks', () => ({ useDefaultModel: () => mockDefaultModelState, })) -vi.mock('./install-from-marketplace', () => ({ +vi.mock('../install-from-marketplace', () => ({ default: () =>
, })) -vi.mock('./provider-added-card', () => ({ +vi.mock('../provider-added-card', () => ({ default: ({ provider }: { provider: { provider: string } }) =>
{provider.provider}
, })) -vi.mock('./provider-added-card/quota-panel', () => ({ +vi.mock('../provider-added-card/quota-panel', () => ({ default: () =>
, })) -vi.mock('./system-model-selector', () => ({ +vi.mock('../system-model-selector', () => ({ default: () =>
, })) diff --git a/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.spec.tsx b/web/app/components/header/account-setting/model-provider-page/__tests__/install-from-marketplace.spec.tsx similarity index 94% rename from web/app/components/header/account-setting/model-provider-page/install-from-marketplace.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/__tests__/install-from-marketplace.spec.tsx index e15e082045..8a732f4e91 100644 --- a/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/__tests__/install-from-marketplace.spec.tsx @@ -1,10 +1,10 @@ import type { Mock } from 'vitest' -import type { ModelProvider } from './declarations' +import type { ModelProvider } from '../declarations' import { fireEvent, render, screen } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' -import { useMarketplaceAllPlugins } from './hooks' -import InstallFromMarketplace from './install-from-marketplace' +import { useMarketplaceAllPlugins } from '../hooks' +import InstallFromMarketplace from '../install-from-marketplace' // Mock dependencies vi.mock('next/link', () => ({ @@ -39,7 +39,7 @@ vi.mock('@/app/components/plugins/provider-card', () => ({ default: ({ payload }: { payload: { name: string } }) =>
{payload.name}
, })) -vi.mock('./hooks', () => ({ +vi.mock('../hooks', () => ({ useMarketplaceAllPlugins: vi.fn(() => ({ plugins: [], isLoading: false, diff --git a/web/app/components/header/account-setting/model-provider-page/utils.spec.ts b/web/app/components/header/account-setting/model-provider-page/__tests__/utils.spec.ts similarity index 99% rename from web/app/components/header/account-setting/model-provider-page/utils.spec.ts rename to web/app/components/header/account-setting/model-provider-page/__tests__/utils.spec.ts index 375ddc4457..63e9d4b21e 100644 --- a/web/app/components/header/account-setting/model-provider-page/utils.spec.ts +++ b/web/app/components/header/account-setting/model-provider-page/__tests__/utils.spec.ts @@ -6,12 +6,12 @@ import { validateModelLoadBalancingCredentials, validateModelProvider, } from '@/service/common' -import { ValidatedStatus } from '../key-validator/declarations' +import { ValidatedStatus } from '../../key-validator/declarations' import { ConfigurationMethodEnum, FormTypeEnum, ModelTypeEnum, -} from './declarations' +} from '../declarations' import { genModelNameFormSchema, genModelTypeFormSchema, @@ -22,7 +22,7 @@ import { sizeFormat, validateCredentials, validateLoadBalancingCredentials, -} from './utils' +} from '../utils' // Mock service/common functions vi.mock('@/service/common', () => ({ diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/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 similarity index 97% rename from web/app/components/header/account-setting/model-provider-page/model-auth/add-credential-in-load-balancing.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-credential-in-load-balancing.spec.tsx index 93f5842a3a..bf26880a8b 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/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 @@ -1,7 +1,7 @@ import type { CustomModel, ModelCredential, ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations' import { fireEvent, render, screen } from '@testing-library/react' import { ConfigurationMethodEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import AddCredentialInLoadBalancing from './add-credential-in-load-balancing' +import AddCredentialInLoadBalancing from '../add-credential-in-load-balancing' vi.mock('@/app/components/header/account-setting/model-provider-page/model-auth', () => ({ Authorized: ({ @@ -112,7 +112,7 @@ describe('AddCredentialInLoadBalancing', () => { // Must invalidate module cache so the component picks up the new mock vi.resetModules() try { - const { default: AddCredentialLB } = await import('./add-credential-in-load-balancing') + const { default: AddCredentialLB } = await import('../add-credential-in-load-balancing') const { container } = render( ({ +vi.mock('../hooks/use-auth', () => ({ useAuth: (_provider: unknown, _configMethod: unknown, _fixedFields: unknown, options: { mode: string }) => { if (options.mode === 'config-custom-model') { return { handleOpenModal: mockHandleOpenModalForAddNewCustomModel } @@ -20,12 +20,12 @@ vi.mock('./hooks/use-auth', () => ({ })) let mockCanAddedModels: { model: string, model_type: string }[] = [] -vi.mock('./hooks/use-custom-models', () => ({ +vi.mock('../hooks/use-custom-models', () => ({ useCanAddedModels: () => mockCanAddedModels, })) // Mock components -vi.mock('../model-icon', () => ({ +vi.mock('../../model-icon', () => ({ default: () =>
, })) diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/config-model.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/config-model.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/model-provider-page/model-auth/config-model.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/config-model.spec.tsx index 5ea651e5e9..85282a6c7e 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/config-model.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/config-model.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import ConfigModel from './config-model' +import ConfigModel from '../config-model' // Mock icons vi.mock('@remixicon/react', () => ({ diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/config-provider.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/config-provider.spec.tsx similarity index 96% rename from web/app/components/header/account-setting/model-provider-page/model-auth/config-provider.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/config-provider.spec.tsx index 8274570c5b..44a2e4398a 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/config-provider.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/config-provider.spec.tsx @@ -1,15 +1,15 @@ import type { ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import ConfigProvider from './config-provider' +import ConfigProvider from '../config-provider' const mockUseCredentialStatus = vi.fn() -vi.mock('./hooks', () => ({ +vi.mock('../hooks', () => ({ useCredentialStatus: () => mockUseCredentialStatus(), })) -vi.mock('./authorized', () => ({ +vi.mock('../authorized', () => ({ default: ({ renderTrigger }: { renderTrigger: () => React.ReactNode }) => (
{renderTrigger()} diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/credential-selector.spec.tsx similarity index 96% rename from web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/credential-selector.spec.tsx index 68d5352857..720bdc2ff3 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/credential-selector.spec.tsx @@ -1,8 +1,8 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import CredentialSelector from './credential-selector' +import CredentialSelector from '../credential-selector' -vi.mock('./authorized/credential-item', () => ({ +vi.mock('../authorized/credential-item', () => ({ default: ({ credential, onItemClick }: { credential: { credential_name: string }, onItemClick?: (c: unknown) => void }) => ( @@ -84,13 +84,13 @@ vi.mock('../model-selector', () => ({ ), })) -vi.mock('./presets-parameter', () => ({ +vi.mock('../presets-parameter', () => ({ default: ({ onSelect }: { onSelect: (id: number) => void }) => ( ), })) -vi.mock('./trigger', () => ({ +vi.mock('../trigger', () => ({ default: () => , })) diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/model-display.spec.tsx similarity index 89% rename from web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/model-display.spec.tsx index ecee8c84e5..c8566f53df 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/model-display.spec.tsx @@ -1,8 +1,8 @@ import { render, screen } from '@testing-library/react' import { vi } from 'vitest' -import ModelDisplay from './model-display' +import ModelDisplay from '../model-display' -vi.mock('../model-name', () => ({ +vi.mock('../../model-name', () => ({ default: ({ modelItem }: { modelItem: { model: string } }) =>
{modelItem.model}
, })) diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/parameter-item.spec.tsx similarity index 98% rename from web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/parameter-item.spec.tsx index e4a355fca0..ecf50a8aad 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/parameter-item.spec.tsx @@ -1,8 +1,8 @@ -import type { ModelParameterRule } from '../declarations' +import type { ModelParameterRule } from '../../declarations' import { fireEvent, render, screen } from '@testing-library/react' -import ParameterItem from './parameter-item' +import ParameterItem from '../parameter-item' -vi.mock('../hooks', () => ({ +vi.mock('../../hooks', () => ({ useLanguage: () => 'en_US', })) diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/presets-parameter.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/presets-parameter.spec.tsx index cb90bb14c9..14cff933e2 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/presets-parameter.spec.tsx @@ -1,6 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import { vi } from 'vitest' -import PresetsParameter from './presets-parameter' +import PresetsParameter from '../presets-parameter' describe('PresetsParameter', () => { beforeEach(() => { diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/status-indicators.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/status-indicators.spec.tsx similarity index 98% rename from web/app/components/header/account-setting/model-provider-page/model-parameter-modal/status-indicators.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/status-indicators.spec.tsx index 620ad7f818..9e28c9e204 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/status-indicators.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/status-indicators.spec.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { vi } from 'vitest' -import StatusIndicators from './status-indicators' +import StatusIndicators from '../status-indicators' let installedPlugins = [{ name: 'demo-plugin', plugin_unique_identifier: 'demo@1.0.0' }] diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/trigger.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/trigger.spec.tsx similarity index 96% rename from web/app/components/header/account-setting/model-provider-page/model-parameter-modal/trigger.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/trigger.spec.tsx index 8a3484cc1f..2fe906c17f 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/trigger.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/__tests__/trigger.spec.tsx @@ -1,9 +1,9 @@ import type { ComponentProps } from 'react' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import Trigger from './trigger' +import Trigger from '../trigger' -vi.mock('../hooks', () => ({ +vi.mock('../../hooks', () => ({ useLanguage: () => 'en_US', })) @@ -13,11 +13,11 @@ vi.mock('@/context/provider-context', () => ({ }), })) -vi.mock('../model-icon', () => ({ +vi.mock('../../model-icon', () => ({ default: () =>
Icon
, })) -vi.mock('../model-name', () => ({ +vi.mock('../../model-name', () => ({ default: ({ modelItem }: { modelItem: { model: string } }) =>
{modelItem.model}
, })) diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/deprecated-model-trigger.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/deprecated-model-trigger.spec.tsx similarity index 94% rename from web/app/components/header/account-setting/model-provider-page/model-selector/deprecated-model-trigger.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/deprecated-model-trigger.spec.tsx index ea31ae192c..c6ba54d800 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/deprecated-model-trigger.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/deprecated-model-trigger.spec.tsx @@ -1,7 +1,7 @@ import { fireEvent, render, screen } from '@testing-library/react' -import DeprecatedModelTrigger from './deprecated-model-trigger' +import DeprecatedModelTrigger from '../deprecated-model-trigger' -vi.mock('../model-icon', () => ({ +vi.mock('../../model-icon', () => ({ default: ({ modelName }: { modelName: string }) => {modelName}, })) diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/empty-trigger.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/empty-trigger.spec.tsx similarity index 95% rename from web/app/components/header/account-setting/model-provider-page/model-selector/empty-trigger.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/empty-trigger.spec.tsx index 9a7b9a2c3f..e560866846 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/empty-trigger.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/empty-trigger.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import EmptyTrigger from './empty-trigger' +import EmptyTrigger from '../empty-trigger' describe('EmptyTrigger', () => { beforeEach(() => { diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/feature-icon.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/feature-icon.spec.tsx similarity index 96% rename from web/app/components/header/account-setting/model-provider-page/model-selector/feature-icon.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/feature-icon.spec.tsx index e785ec58c7..8e68ef11dc 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/feature-icon.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/feature-icon.spec.tsx @@ -2,8 +2,8 @@ import { fireEvent, render, screen } from '@testing-library/react' import { ModelFeatureEnum, ModelFeatureTextEnum, -} from '../declarations' -import FeatureIcon from './feature-icon' +} from '../../declarations' +import FeatureIcon from '../feature-icon' describe('FeatureIcon', () => { beforeEach(() => { diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx similarity index 92% rename from web/app/components/header/account-setting/model-provider-page/model-selector/index.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx index 0491bb0849..27239a7079 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx @@ -1,25 +1,25 @@ -import type { Model, ModelItem } from '../declarations' +import type { Model, ModelItem } from '../../declarations' import { fireEvent, render, screen } from '@testing-library/react' import { ConfigurationMethodEnum, ModelStatusEnum, ModelTypeEnum, -} from '../declarations' -import ModelSelector from './index' +} from '../../declarations' +import ModelSelector from '../index' -vi.mock('./model-trigger', () => ({ +vi.mock('../model-trigger', () => ({ default: () =>
model-trigger
, })) -vi.mock('./deprecated-model-trigger', () => ({ +vi.mock('../deprecated-model-trigger', () => ({ default: ({ modelName }: { modelName: string }) =>
{`deprecated:${modelName}`}
, })) -vi.mock('./empty-trigger', () => ({ +vi.mock('../empty-trigger', () => ({ default: () =>
empty-trigger
, })) -vi.mock('./popup', () => ({ +vi.mock('../popup', () => ({ default: ({ onHide, onSelect }: { onHide: () => void, onSelect: (provider: string, model: ModelItem) => void }) => ( <> @@ -40,11 +40,11 @@ vi.mock('./model-list', () => ({ ), })) -vi.mock('../provider-icon', () => ({ +vi.mock('../../provider-icon', () => ({ default: () =>
, })) -vi.mock('../model-badge', () => ({ +vi.mock('../../model-badge', () => ({ default: ({ children }: { children: string }) =>
{children}
, })) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx similarity index 95% rename from web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx index a1ab77b16f..2bac786391 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/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,8 +1,8 @@ -import type { ModelItem, ModelProvider } from '../declarations' +import type { ModelItem, ModelProvider } from '../../declarations' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { disableModel, enableModel } from '@/service/common' -import { ModelStatusEnum } from '../declarations' -import ModelListItem from './model-list-item' +import { ModelStatusEnum } from '../../declarations' +import ModelListItem from '../model-list-item' let mockModelLoadBalancingEnabled = false let mockPlanType: string = 'pro' @@ -25,19 +25,19 @@ vi.mock('@/service/common', () => ({ disableModel: vi.fn(), })) -vi.mock('../hooks', () => ({ +vi.mock('../../hooks', () => ({ useUpdateModelList: () => vi.fn(), })) -vi.mock('../model-icon', () => ({ +vi.mock('../../model-icon', () => ({ default: () =>
, })) -vi.mock('../model-name', () => ({ +vi.mock('../../model-name', () => ({ default: ({ children }: { children: React.ReactNode }) =>
{children}
, })) -vi.mock('../model-auth', () => ({ +vi.mock('../../model-auth', () => ({ ConfigModel: ({ onClick }: { onClick: () => void }) => ( ), diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.spec.tsx rename to web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx index cebd18ec2a..70a0cb985a 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx @@ -1,7 +1,7 @@ -import type { ModelItem, ModelProvider } from '../declarations' +import type { ModelItem, ModelProvider } from '../../declarations' import { fireEvent, render, screen } from '@testing-library/react' -import { ConfigurationMethodEnum } from '../declarations' -import ModelList from './model-list' +import { ConfigurationMethodEnum } from '../../declarations' +import ModelList from '../model-list' const mockSetShowModelLoadBalancingModal = vi.fn() let mockIsCurrentWorkspaceManager = true @@ -17,7 +17,7 @@ vi.mock('@/context/modal-context', () => ({ selector({ setShowModelLoadBalancingModal: mockSetShowModelLoadBalancingModal }), })) -vi.mock('./model-list-item', () => ({ +vi.mock('../model-list-item', () => ({ default: ({ model, onModifyLoadBalancing }: { model: ModelItem, onModifyLoadBalancing: (model: ModelItem) => void }) => ( ), diff --git a/web/app/components/header/account-setting/plugin-page/SerpapiPlugin.spec.tsx b/web/app/components/header/account-setting/plugin-page/__tests__/SerpapiPlugin.spec.tsx similarity index 97% rename from web/app/components/header/account-setting/plugin-page/SerpapiPlugin.spec.tsx rename to web/app/components/header/account-setting/plugin-page/__tests__/SerpapiPlugin.spec.tsx index 03c568e71e..2a35146e4f 100644 --- a/web/app/components/header/account-setting/plugin-page/SerpapiPlugin.spec.tsx +++ b/web/app/components/header/account-setting/plugin-page/__tests__/SerpapiPlugin.spec.tsx @@ -2,8 +2,8 @@ import type { PluginProvider } from '@/models/common' import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' import { useToastContext } from '@/app/components/base/toast/context' import { useAppContext } from '@/context/app-context' -import SerpapiPlugin from './SerpapiPlugin' -import { updatePluginKey, validatePluginKey } from './utils' +import SerpapiPlugin from '../SerpapiPlugin' +import { updatePluginKey, validatePluginKey } from '../utils' const mockEventEmitter = vi.hoisted(() => { let subscriber: ((value: string) => void) | undefined @@ -32,7 +32,7 @@ vi.mock('@/context/app-context', () => ({ useAppContext: vi.fn(), })) -vi.mock('./utils', () => ({ +vi.mock('../utils', () => ({ updatePluginKey: vi.fn(), validatePluginKey: vi.fn(), })) diff --git a/web/app/components/header/account-setting/plugin-page/index.spec.tsx b/web/app/components/header/account-setting/plugin-page/__tests__/index.spec.tsx similarity index 96% rename from web/app/components/header/account-setting/plugin-page/index.spec.tsx rename to web/app/components/header/account-setting/plugin-page/__tests__/index.spec.tsx index 68592ab142..45047d2894 100644 --- a/web/app/components/header/account-setting/plugin-page/index.spec.tsx +++ b/web/app/components/header/account-setting/plugin-page/__tests__/index.spec.tsx @@ -1,8 +1,8 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { useState } from 'react' import { useAppContext } from '@/context/app-context' -import PluginPage from './index' -import { updatePluginKey, validatePluginKey } from './utils' +import PluginPage from '../index' +import { updatePluginKey, validatePluginKey } from '../utils' const mockUsePluginProviders = vi.hoisted(() => vi.fn()) @@ -33,7 +33,7 @@ vi.mock('@/context/event-emitter', () => ({ }), })) -vi.mock('./utils', () => ({ +vi.mock('../utils', () => ({ updatePluginKey: vi.fn(), validatePluginKey: vi.fn(), })) diff --git a/web/app/components/header/account-setting/plugin-page/utils.spec.ts b/web/app/components/header/account-setting/plugin-page/__tests__/utils.spec.ts similarity index 94% rename from web/app/components/header/account-setting/plugin-page/utils.spec.ts rename to web/app/components/header/account-setting/plugin-page/__tests__/utils.spec.ts index 720bc956b8..e49a9f41c9 100644 --- a/web/app/components/header/account-setting/plugin-page/utils.spec.ts +++ b/web/app/components/header/account-setting/plugin-page/__tests__/utils.spec.ts @@ -1,6 +1,6 @@ import { updatePluginProviderAIKey, validatePluginProviderKey } from '@/service/common' -import { ValidatedStatus } from '../key-validator/declarations' -import { updatePluginKey, validatePluginKey } from './utils' +import { ValidatedStatus } from '../../key-validator/declarations' +import { updatePluginKey, validatePluginKey } from '../utils' vi.mock('@/service/common', () => ({ validatePluginProviderKey: vi.fn(), diff --git a/web/app/components/header/app-back/index.spec.tsx b/web/app/components/header/app-back/__tests__/index.spec.tsx similarity index 97% rename from web/app/components/header/app-back/index.spec.tsx rename to web/app/components/header/app-back/__tests__/index.spec.tsx index d80ae1240c..5333e0542b 100644 --- a/web/app/components/header/app-back/index.spec.tsx +++ b/web/app/components/header/app-back/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ import type { App } from '@/types/app' import { fireEvent, render, screen } from '@testing-library/react' -import AppBack from './index' +import AppBack from '../index' describe('AppBack', () => { const mockApp = { diff --git a/web/app/components/header/app-nav/index.spec.tsx b/web/app/components/header/app-nav/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/app-nav/index.spec.tsx rename to web/app/components/header/app-nav/__tests__/index.spec.tsx index af0f99cb85..0ccb468670 100644 --- a/web/app/components/header/app-nav/index.spec.tsx +++ b/web/app/components/header/app-nav/__tests__/index.spec.tsx @@ -5,7 +5,7 @@ import { useStore as useAppStore } from '@/app/components/app/store' import { useAppContext } from '@/context/app-context' import { useInfiniteAppList } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' -import AppNav from './index' +import AppNav from '../index' vi.mock('next/navigation', () => ({ useParams: vi.fn(), @@ -83,7 +83,7 @@ vi.mock('@/app/components/app/create-from-dsl-modal', () => ({ : null, })) -vi.mock('../nav', () => ({ +vi.mock('../../nav', () => ({ default: ({ onCreate, onLoadMore, diff --git a/web/app/components/header/app-selector/index.spec.tsx b/web/app/components/header/app-selector/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/app-selector/index.spec.tsx rename to web/app/components/header/app-selector/__tests__/index.spec.tsx index f301de4580..676aba7023 100644 --- a/web/app/components/header/app-selector/index.spec.tsx +++ b/web/app/components/header/app-selector/__tests__/index.spec.tsx @@ -3,7 +3,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react' import { useRouter } from 'next/navigation' import { vi } from 'vitest' import { useAppContext } from '@/context/app-context' -import AppSelector from './index' +import AppSelector from '../index' // Mock next/navigation vi.mock('next/navigation', () => ({ diff --git a/web/app/components/header/dataset-nav/index.spec.tsx b/web/app/components/header/dataset-nav/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/dataset-nav/index.spec.tsx rename to web/app/components/header/dataset-nav/__tests__/index.spec.tsx index 8c1b5952a7..a551538e98 100644 --- a/web/app/components/header/dataset-nav/index.spec.tsx +++ b/web/app/components/header/dataset-nav/__tests__/index.spec.tsx @@ -10,7 +10,7 @@ import { useDatasetDetail, useDatasetList, } from '@/service/knowledge/use-dataset' -import DatasetNav from './index' +import DatasetNav from '../index' vi.mock('next/navigation', () => ({ useParams: vi.fn(), diff --git a/web/app/components/header/env-nav/index.spec.tsx b/web/app/components/header/env-nav/__tests__/index.spec.tsx similarity index 97% rename from web/app/components/header/env-nav/index.spec.tsx rename to web/app/components/header/env-nav/__tests__/index.spec.tsx index 2b13af1016..81076e773f 100644 --- a/web/app/components/header/env-nav/index.spec.tsx +++ b/web/app/components/header/env-nav/__tests__/index.spec.tsx @@ -2,7 +2,7 @@ import type { AppContextValue } from '@/context/app-context' import { render, screen } from '@testing-library/react' import { vi } from 'vitest' import { useAppContext } from '@/context/app-context' -import EnvNav from './index' +import EnvNav from '../index' vi.mock('@/context/app-context', () => ({ useAppContext: vi.fn(), diff --git a/web/app/components/header/explore-nav/index.spec.tsx b/web/app/components/header/explore-nav/__tests__/index.spec.tsx similarity index 97% rename from web/app/components/header/explore-nav/index.spec.tsx rename to web/app/components/header/explore-nav/__tests__/index.spec.tsx index 65a3f88f5e..79285cf53e 100644 --- a/web/app/components/header/explore-nav/index.spec.tsx +++ b/web/app/components/header/explore-nav/__tests__/index.spec.tsx @@ -1,7 +1,7 @@ import type { Mock } from 'vitest' import { render, screen } from '@testing-library/react' import { useSelectedLayoutSegment } from 'next/navigation' -import ExploreNav from './index' +import ExploreNav from '../index' vi.mock('next/navigation', () => ({ useSelectedLayoutSegment: vi.fn(), diff --git a/web/app/components/header/github-star/index.spec.tsx b/web/app/components/header/github-star/__tests__/index.spec.tsx similarity index 98% rename from web/app/components/header/github-star/index.spec.tsx rename to web/app/components/header/github-star/__tests__/index.spec.tsx index f60ced4147..1790f31542 100644 --- a/web/app/components/header/github-star/index.spec.tsx +++ b/web/app/components/header/github-star/__tests__/index.spec.tsx @@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import nock from 'nock' import * as React from 'react' -import GithubStar from './index' +import GithubStar from '../index' const GITHUB_HOST = 'https://api.github.com' const GITHUB_PATH = '/repos/langgenius/dify' diff --git a/web/app/components/header/indicator/index.spec.tsx b/web/app/components/header/indicator/__tests__/index.spec.tsx similarity index 98% rename from web/app/components/header/indicator/index.spec.tsx rename to web/app/components/header/indicator/__tests__/index.spec.tsx index b5921d8fc0..ffb2ade8d3 100644 --- a/web/app/components/header/indicator/index.spec.tsx +++ b/web/app/components/header/indicator/__tests__/index.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import Indicator from './index' +import Indicator from '../index' describe('Indicator', () => { it('should render with default props', () => { diff --git a/web/app/components/header/license-env/index.spec.tsx b/web/app/components/header/license-env/__tests__/index.spec.tsx similarity index 98% rename from web/app/components/header/license-env/index.spec.tsx rename to web/app/components/header/license-env/__tests__/index.spec.tsx index df3559909b..cec4af60d9 100644 --- a/web/app/components/header/license-env/index.spec.tsx +++ b/web/app/components/header/license-env/__tests__/index.spec.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react' import dayjs from 'dayjs' import { useGlobalPublicStore } from '@/context/global-public-context' import { defaultSystemFeatures, LicenseStatus } from '@/types/feature' -import LicenseNav from './index' +import LicenseNav from '../index' describe('LicenseNav', () => { beforeEach(() => { diff --git a/web/app/components/header/nav/index.spec.tsx b/web/app/components/header/nav/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/nav/index.spec.tsx rename to web/app/components/header/nav/__tests__/index.spec.tsx index ab530a4a86..a47dc711c8 100644 --- a/web/app/components/header/nav/index.spec.tsx +++ b/web/app/components/header/nav/__tests__/index.spec.tsx @@ -1,4 +1,4 @@ -import type { NavItem } from './nav-selector' +import type { NavItem } from '../nav-selector' import type { AppContextValue } from '@/context/app-context' import { act, @@ -13,7 +13,7 @@ import { vi } from 'vitest' import { useStore as useAppStore } from '@/app/components/app/store' import { useAppContext } from '@/context/app-context' import { AppModeEnum } from '@/types/app' -import Nav from './index' +import Nav from '../index' vi.mock('@headlessui/react', () => { type MenuContextValue = { open: boolean, setOpen: (open: boolean) => void } diff --git a/web/app/components/header/nav/nav-selector/index.spec.tsx b/web/app/components/header/nav/nav-selector/__tests__/index.spec.tsx similarity index 98% rename from web/app/components/header/nav/nav-selector/index.spec.tsx rename to web/app/components/header/nav/nav-selector/__tests__/index.spec.tsx index d613d4bf73..55d77389c6 100644 --- a/web/app/components/header/nav/nav-selector/index.spec.tsx +++ b/web/app/components/header/nav/nav-selector/__tests__/index.spec.tsx @@ -1,4 +1,4 @@ -import type { INavSelectorProps, NavItem } from './index' +import type { INavSelectorProps, NavItem } from '../index' import type { AppContextValue } from '@/context/app-context' import { act, fireEvent, render, screen } from '@testing-library/react' import { useRouter } from 'next/navigation' @@ -7,7 +7,7 @@ import { vi } from 'vitest' import { useStore as useAppStore } from '@/app/components/app/store' import { useAppContext } from '@/context/app-context' import { AppModeEnum } from '@/types/app' -import NavSelector from './index' +import NavSelector from '../index' vi.mock('@headlessui/react', () => { type MenuContextValue = { open: boolean, setOpen: (open: boolean) => void } diff --git a/web/app/components/header/plan-badge/index.spec.tsx b/web/app/components/header/plan-badge/__tests__/index.spec.tsx similarity index 97% rename from web/app/components/header/plan-badge/index.spec.tsx rename to web/app/components/header/plan-badge/__tests__/index.spec.tsx index 80159588f5..3abb791340 100644 --- a/web/app/components/header/plan-badge/index.spec.tsx +++ b/web/app/components/header/plan-badge/__tests__/index.spec.tsx @@ -3,8 +3,8 @@ import { fireEvent, render, screen } from '@testing-library/react' import { vi } from 'vitest' import { createMockProviderContextValue } from '@/__mocks__/provider-context' import { useProviderContext } from '@/context/provider-context' -import { Plan } from '../../billing/type' -import PlanBadge from './index' +import { Plan } from '../../../billing/type' +import PlanBadge from '../index' vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(), diff --git a/web/app/components/header/plugins-nav/index.spec.tsx b/web/app/components/header/plugins-nav/__tests__/index.spec.tsx similarity index 99% rename from web/app/components/header/plugins-nav/index.spec.tsx rename to web/app/components/header/plugins-nav/__tests__/index.spec.tsx index f76f579aa9..009e573eb1 100644 --- a/web/app/components/header/plugins-nav/index.spec.tsx +++ b/web/app/components/header/plugins-nav/__tests__/index.spec.tsx @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react' import { useSelectedLayoutSegment } from 'next/navigation' import { usePluginTaskStatus } from '@/app/components/plugins/plugin-page/plugin-tasks/hooks' -import PluginsNav from './index' +import PluginsNav from '../index' vi.mock('next/navigation', () => ({ useSelectedLayoutSegment: vi.fn(), diff --git a/web/app/components/header/tools-nav/index.spec.tsx b/web/app/components/header/tools-nav/__tests__/index.spec.tsx similarity index 98% rename from web/app/components/header/tools-nav/index.spec.tsx rename to web/app/components/header/tools-nav/__tests__/index.spec.tsx index dadb55eac5..e3ceef43a4 100644 --- a/web/app/components/header/tools-nav/index.spec.tsx +++ b/web/app/components/header/tools-nav/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import ToolsNav from './index' +import ToolsNav from '../index' const mockUseSelectedLayoutSegment = vi.fn() vi.mock('next/navigation', () => ({ diff --git a/web/app/components/header/utils/util.spec.ts b/web/app/components/header/utils/__tests__/util.spec.ts similarity index 97% rename from web/app/components/header/utils/util.spec.ts rename to web/app/components/header/utils/__tests__/util.spec.ts index e80d0151ee..54af59cd8e 100644 --- a/web/app/components/header/utils/util.spec.ts +++ b/web/app/components/header/utils/__tests__/util.spec.ts @@ -1,4 +1,4 @@ -import { generateMailToLink, mailToSupport } from './util' +import { generateMailToLink, mailToSupport } from '../util' describe('generateMailToLink', () => { // Email-only: both subject and body branches false diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index a3940064b1..f3be034e13 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -2920,11 +2920,6 @@ "count": 1 } }, - "app/components/custom/custom-web-app-brand/index.tsx": { - "ts/no-explicit-any": { - "count": 2 - } - }, "app/components/datasets/chunk.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 3 diff --git a/web/scripts/components-coverage-thresholds.mjs b/web/scripts/components-coverage-thresholds.mjs index b73de41f12..fedd579947 100644 --- a/web/scripts/components-coverage-thresholds.mjs +++ b/web/scripts/components-coverage-thresholds.mjs @@ -44,10 +44,10 @@ export const COMPONENT_MODULE_THRESHOLDS = { branches: 95, }, 'custom': { - lines: 70, - statements: 70, - functions: 70, - branches: 80, + lines: 95, + statements: 95, + functions: 95, + branches: 95, }, 'datasets': { lines: 95,