From 0cb696b208efb97b12fcf7ed5c4371338ae621e4 Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 8 Dec 2025 17:23:45 +0800 Subject: [PATCH] chore: add provider context mock (#29201) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- web/__mocks__/provider-context.ts | 47 +++++++++++++ web/context/provider-context-mock.spec.tsx | 82 ++++++++++++++++++++++ web/context/provider-context-mock.tsx | 18 +++++ web/context/provider-context.tsx | 9 ++- web/testing/testing.md | 2 + 5 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 web/__mocks__/provider-context.ts create mode 100644 web/context/provider-context-mock.spec.tsx create mode 100644 web/context/provider-context-mock.tsx diff --git a/web/__mocks__/provider-context.ts b/web/__mocks__/provider-context.ts new file mode 100644 index 0000000000..594fe38f14 --- /dev/null +++ b/web/__mocks__/provider-context.ts @@ -0,0 +1,47 @@ +import { merge, noop } from 'lodash-es' +import { defaultPlan } from '@/app/components/billing/config' +import { baseProviderContextValue } from '@/context/provider-context' +import type { ProviderContextState } from '@/context/provider-context' +import type { Plan, UsagePlanInfo } from '@/app/components/billing/type' + +export const createMockProviderContextValue = (overrides: Partial = {}): ProviderContextState => { + const merged = merge({}, baseProviderContextValue, overrides) + + return { + ...merged, + refreshModelProviders: merged.refreshModelProviders ?? noop, + onPlanInfoChanged: merged.onPlanInfoChanged ?? noop, + refreshLicenseLimit: merged.refreshLicenseLimit ?? noop, + } +} + +export const createMockPlan = (plan: Plan): ProviderContextState => + createMockProviderContextValue({ + plan: merge({}, defaultPlan, { + type: plan, + }), + }) + +export const createMockPlanUsage = (usage: UsagePlanInfo, ctx: Partial): ProviderContextState => + createMockProviderContextValue({ + ...ctx, + plan: merge(ctx.plan, { + usage, + }), + }) + +export const createMockPlanTotal = (total: UsagePlanInfo, ctx: Partial): ProviderContextState => + createMockProviderContextValue({ + ...ctx, + plan: merge(ctx.plan, { + total, + }), + }) + +export const createMockPlanReset = (reset: Partial, ctx: Partial): ProviderContextState => + createMockProviderContextValue({ + ...ctx, + plan: merge(ctx?.plan, { + reset, + }), + }) diff --git a/web/context/provider-context-mock.spec.tsx b/web/context/provider-context-mock.spec.tsx new file mode 100644 index 0000000000..ca7c6b884e --- /dev/null +++ b/web/context/provider-context-mock.spec.tsx @@ -0,0 +1,82 @@ +import { render } from '@testing-library/react' +import type { UsagePlanInfo } from '@/app/components/billing/type' +import { Plan } from '@/app/components/billing/type' +import ProviderContextMock from './provider-context-mock' +import { createMockPlan, createMockPlanReset, createMockPlanTotal, createMockPlanUsage } from '@/__mocks__/provider-context' + +let mockPlan: Plan = Plan.sandbox +const usage: UsagePlanInfo = { + vectorSpace: 1, + buildApps: 10, + teamMembers: 1, + annotatedResponse: 1, + documentsUploadQuota: 0, + apiRateLimit: 0, + triggerEvents: 0, +} + +const total: UsagePlanInfo = { + vectorSpace: 100, + buildApps: 100, + teamMembers: 10, + annotatedResponse: 100, + documentsUploadQuota: 0, + apiRateLimit: 0, + triggerEvents: 0, +} + +const reset = { + apiRateLimit: 100, + triggerEvents: 100, +} + +jest.mock('@/context/provider-context', () => ({ + useProviderContext: () => { + const withPlan = createMockPlan(mockPlan) + const withUsage = createMockPlanUsage(usage, withPlan) + const withTotal = createMockPlanTotal(total, withUsage) + const withReset = createMockPlanReset(reset, withTotal) + console.log(JSON.stringify(withReset.plan, null, 2)) + return withReset + }, +})) + +const renderWithPlan = (plan: Plan) => { + mockPlan = plan + return render() +} + +describe('ProviderContextMock', () => { + beforeEach(() => { + mockPlan = Plan.sandbox + jest.clearAllMocks() + }) + it('should display sandbox plan type when mocked with sandbox plan', async () => { + const { getByTestId } = renderWithPlan(Plan.sandbox) + expect(getByTestId('plan-type').textContent).toBe(Plan.sandbox) + }) + it('should display team plan type when mocked with team plan', () => { + const { getByTestId } = renderWithPlan(Plan.team) + expect(getByTestId('plan-type').textContent).toBe(Plan.team) + }) + it('should provide usage info from mocked plan', () => { + const { getByTestId } = renderWithPlan(Plan.team) + const buildApps = getByTestId('plan-usage-build-apps').textContent + + expect(Number(buildApps as string)).toEqual(usage.buildApps) + }) + + it('should provide total info from mocked plan', () => { + const { getByTestId } = renderWithPlan(Plan.team) + const buildApps = getByTestId('plan-total-build-apps').textContent + + expect(Number(buildApps as string)).toEqual(total.buildApps) + }) + + it('should provide reset info from mocked plan', () => { + const { getByTestId } = renderWithPlan(Plan.team) + const apiRateLimit = getByTestId('plan-reset-api-rate-limit').textContent + + expect(Number(apiRateLimit as string)).toEqual(reset.apiRateLimit) + }) +}) diff --git a/web/context/provider-context-mock.tsx b/web/context/provider-context-mock.tsx new file mode 100644 index 0000000000..b42847a9ec --- /dev/null +++ b/web/context/provider-context-mock.tsx @@ -0,0 +1,18 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useProviderContext } from '@/context/provider-context' + +const ProviderContextMock: FC = () => { + const { plan } = useProviderContext() + + return ( +
+
{plan.type}
+
{plan.usage.buildApps}
+
{plan.total.buildApps}
+
{plan.reset.apiRateLimit}
+
+ ) +} +export default React.memo(ProviderContextMock) diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index 26617921f1..70944d85f1 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -30,7 +30,7 @@ import { noop } from 'lodash-es' import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils' import { ZENDESK_FIELD_IDS } from '@/config' -type ProviderContextState = { +export type ProviderContextState = { modelProviders: ModelProvider[] refreshModelProviders: () => void textGenerationModelList: Model[] @@ -66,7 +66,8 @@ type ProviderContextState = { isAllowTransferWorkspace: boolean isAllowPublishAsCustomKnowledgePipelineTemplate: boolean } -const ProviderContext = createContext({ + +export const baseProviderContextValue: ProviderContextState = { modelProviders: [], refreshModelProviders: noop, textGenerationModelList: [], @@ -96,7 +97,9 @@ const ProviderContext = createContext({ refreshLicenseLimit: noop, isAllowTransferWorkspace: false, isAllowPublishAsCustomKnowledgePipelineTemplate: false, -}) +} + +const ProviderContext = createContext(baseProviderContextValue) export const useProviderContext = () => useContext(ProviderContext) diff --git a/web/testing/testing.md b/web/testing/testing.md index e2df86c653..f03451230d 100644 --- a/web/testing/testing.md +++ b/web/testing/testing.md @@ -146,6 +146,8 @@ Treat component state as part of the public behavior: confirm the initial render - ✅ Reset shared stores (React context, Zustand, TanStack Query cache) between tests to avoid leaking state. Prefer helper factory functions over module-level singletons in specs. - ✅ For hooks that read from context, use `renderHook` with a custom wrapper that supplies required providers. +If it's need to mock some common context provider used across many components (for example, `ProviderContext`), put it in __mocks__/context(for example, `__mocks__/context/provider-context`). To dynamically control the mock behavior (for example, toggling plan type), use module-level variables to track state and change them(for example, `context/provier-context-mock.spec.tsx`). + ### 4. Performance Optimization Cover memoized callbacks or values only when they influence observable behavior—memoized children, subscription updates, expensive computations. Trigger realistic re-renders and assert the outcomes (avoided rerenders, reused results) instead of inspecting hook internals.