chore: add provider context mock (#29201)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Joel 2025-12-08 17:23:45 +08:00 committed by GitHub
parent b466d8da92
commit 0cb696b208
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 155 additions and 3 deletions

View File

@ -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> = {}): 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>): ProviderContextState =>
createMockProviderContextValue({
...ctx,
plan: merge(ctx.plan, {
usage,
}),
})
export const createMockPlanTotal = (total: UsagePlanInfo, ctx: Partial<ProviderContextState>): ProviderContextState =>
createMockProviderContextValue({
...ctx,
plan: merge(ctx.plan, {
total,
}),
})
export const createMockPlanReset = (reset: Partial<ProviderContextState['plan']['reset']>, ctx: Partial<ProviderContextState>): ProviderContextState =>
createMockProviderContextValue({
...ctx,
plan: merge(ctx?.plan, {
reset,
}),
})

View File

@ -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(<ProviderContextMock />)
}
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)
})
})

View File

@ -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 (
<div>
<div data-testid="plan-type">{plan.type}</div>
<div data-testid="plan-usage-build-apps">{plan.usage.buildApps}</div>
<div data-testid="plan-total-build-apps">{plan.total.buildApps}</div>
<div data-testid="plan-reset-api-rate-limit">{plan.reset.apiRateLimit}</div>
</div>
)
}
export default React.memo(ProviderContextMock)

View File

@ -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<ProviderContextState>({
export const baseProviderContextValue: ProviderContextState = {
modelProviders: [],
refreshModelProviders: noop,
textGenerationModelList: [],
@ -96,7 +97,9 @@ const ProviderContext = createContext<ProviderContextState>({
refreshLicenseLimit: noop,
isAllowTransferWorkspace: false,
isAllowPublishAsCustomKnowledgePipelineTemplate: false,
})
}
const ProviderContext = createContext<ProviderContextState>(baseProviderContextValue)
export const useProviderContext = () => useContext(ProviderContext)

View File

@ -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.