mirror of
https://github.com/langgenius/dify.git
synced 2025-11-24 00:42:41 +00:00
254 lines
8.3 KiB
TypeScript
254 lines
8.3 KiB
TypeScript
/**
|
|
* Test suite for React context creation utilities
|
|
*
|
|
* This module provides helper functions to create React contexts with better type safety
|
|
* and automatic error handling when context is used outside of its provider.
|
|
*
|
|
* Two variants are provided:
|
|
* - createCtx: Standard React context using useContext/createContext
|
|
* - createSelectorCtx: Context with selector support using use-context-selector library
|
|
*/
|
|
import React from 'react'
|
|
import { renderHook } from '@testing-library/react'
|
|
import { createCtx, createSelectorCtx } from './context'
|
|
|
|
describe('Context Utilities', () => {
|
|
describe('createCtx', () => {
|
|
/**
|
|
* Test that createCtx creates a valid context with provider and hook
|
|
* The function should return a tuple with [Provider, useContextValue, Context]
|
|
* plus named properties for easier access
|
|
*/
|
|
it('should create context with provider and hook', () => {
|
|
type TestContextValue = { value: string }
|
|
const [Provider, useTestContext, Context] = createCtx<TestContextValue>({
|
|
name: 'Test',
|
|
})
|
|
|
|
expect(Provider).toBeDefined()
|
|
expect(useTestContext).toBeDefined()
|
|
expect(Context).toBeDefined()
|
|
})
|
|
|
|
/**
|
|
* Test that the context hook returns the provided value correctly
|
|
* when used within the context provider
|
|
*/
|
|
it('should provide and consume context value', () => {
|
|
type TestContextValue = { value: string }
|
|
const [Provider, useTestContext] = createCtx<TestContextValue>({
|
|
name: 'Test',
|
|
})
|
|
|
|
const testValue = { value: 'test-value' }
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement(Provider, { value: testValue }, children)
|
|
|
|
const { result } = renderHook(() => useTestContext(), { wrapper })
|
|
|
|
expect(result.current).toEqual(testValue)
|
|
})
|
|
|
|
/**
|
|
* Test that accessing context outside of provider throws an error
|
|
* This ensures developers are notified when they forget to wrap components
|
|
*/
|
|
it('should throw error when used outside provider', () => {
|
|
type TestContextValue = { value: string }
|
|
const [, useTestContext] = createCtx<TestContextValue>({
|
|
name: 'Test',
|
|
})
|
|
|
|
// Suppress console.error for this test
|
|
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => { /* suppress error */ })
|
|
|
|
expect(() => {
|
|
renderHook(() => useTestContext())
|
|
}).toThrow('No Test context found.')
|
|
|
|
consoleError.mockRestore()
|
|
})
|
|
|
|
/**
|
|
* Test that context works with default values
|
|
* When a default value is provided, it should be accessible without a provider
|
|
*/
|
|
it('should use default value when provided', () => {
|
|
type TestContextValue = { value: string }
|
|
const defaultValue = { value: 'default' }
|
|
const [, useTestContext] = createCtx<TestContextValue>({
|
|
name: 'Test',
|
|
defaultValue,
|
|
})
|
|
|
|
const { result } = renderHook(() => useTestContext())
|
|
|
|
expect(result.current).toEqual(defaultValue)
|
|
})
|
|
|
|
/**
|
|
* Test that the returned tuple has named properties for convenience
|
|
* This allows destructuring or property access based on preference
|
|
*/
|
|
it('should expose named properties', () => {
|
|
type TestContextValue = { value: string }
|
|
const result = createCtx<TestContextValue>({ name: 'Test' })
|
|
|
|
expect(result.provider).toBe(result[0])
|
|
expect(result.useContextValue).toBe(result[1])
|
|
expect(result.context).toBe(result[2])
|
|
})
|
|
|
|
/**
|
|
* Test context with complex data types
|
|
* Ensures type safety is maintained with nested objects and arrays
|
|
*/
|
|
it('should handle complex context values', () => {
|
|
type ComplexContext = {
|
|
user: { id: string; name: string }
|
|
settings: { theme: string; locale: string }
|
|
actions: Array<() => void>
|
|
}
|
|
|
|
const [Provider, useComplexContext] = createCtx<ComplexContext>({
|
|
name: 'Complex',
|
|
})
|
|
|
|
const complexValue: ComplexContext = {
|
|
user: { id: '123', name: 'Test User' },
|
|
settings: { theme: 'dark', locale: 'en-US' },
|
|
actions: [
|
|
() => { /* empty action 1 */ },
|
|
() => { /* empty action 2 */ },
|
|
],
|
|
}
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement(Provider, { value: complexValue }, children)
|
|
|
|
const { result } = renderHook(() => useComplexContext(), { wrapper })
|
|
|
|
expect(result.current).toEqual(complexValue)
|
|
expect(result.current.user.id).toBe('123')
|
|
expect(result.current.settings.theme).toBe('dark')
|
|
expect(result.current.actions).toHaveLength(2)
|
|
})
|
|
|
|
/**
|
|
* Test that context updates propagate to consumers
|
|
* When provider value changes, hooks should receive the new value
|
|
*/
|
|
it('should update when context value changes', () => {
|
|
type TestContextValue = { count: number }
|
|
const [Provider, useTestContext] = createCtx<TestContextValue>({
|
|
name: 'Test',
|
|
})
|
|
|
|
let value = { count: 0 }
|
|
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement(Provider, { value }, children)
|
|
|
|
const { result, rerender } = renderHook(() => useTestContext(), { wrapper })
|
|
|
|
expect(result.current.count).toBe(0)
|
|
|
|
value = { count: 5 }
|
|
rerender()
|
|
|
|
expect(result.current.count).toBe(5)
|
|
})
|
|
})
|
|
|
|
describe('createSelectorCtx', () => {
|
|
/**
|
|
* Test that createSelectorCtx creates a valid context with selector support
|
|
* This variant uses use-context-selector for optimized re-renders
|
|
*/
|
|
it('should create selector context with provider and hook', () => {
|
|
type TestContextValue = { value: string }
|
|
const [Provider, useTestContext, Context] = createSelectorCtx<TestContextValue>({
|
|
name: 'SelectorTest',
|
|
})
|
|
|
|
expect(Provider).toBeDefined()
|
|
expect(useTestContext).toBeDefined()
|
|
expect(Context).toBeDefined()
|
|
})
|
|
|
|
/**
|
|
* Test that selector context provides and consumes values correctly
|
|
* The API should be identical to createCtx for basic usage
|
|
*/
|
|
it('should provide and consume context value with selector', () => {
|
|
type TestContextValue = { value: string }
|
|
const [Provider, useTestContext] = createSelectorCtx<TestContextValue>({
|
|
name: 'SelectorTest',
|
|
})
|
|
|
|
const testValue = { value: 'selector-test' }
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement(Provider, { value: testValue }, children)
|
|
|
|
const { result } = renderHook(() => useTestContext(), { wrapper })
|
|
|
|
expect(result.current).toEqual(testValue)
|
|
})
|
|
|
|
/**
|
|
* Test error handling for selector context
|
|
* Should throw error when used outside provider, same as createCtx
|
|
*/
|
|
it('should throw error when used outside provider', () => {
|
|
type TestContextValue = { value: string }
|
|
const [, useTestContext] = createSelectorCtx<TestContextValue>({
|
|
name: 'SelectorTest',
|
|
})
|
|
|
|
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => { /* suppress error */ })
|
|
|
|
expect(() => {
|
|
renderHook(() => useTestContext())
|
|
}).toThrow('No SelectorTest context found.')
|
|
|
|
consoleError.mockRestore()
|
|
})
|
|
|
|
/**
|
|
* Test that selector context works with default values
|
|
*/
|
|
it('should use default value when provided', () => {
|
|
type TestContextValue = { value: string }
|
|
const defaultValue = { value: 'selector-default' }
|
|
const [, useTestContext] = createSelectorCtx<TestContextValue>({
|
|
name: 'SelectorTest',
|
|
defaultValue,
|
|
})
|
|
|
|
const { result } = renderHook(() => useTestContext())
|
|
|
|
expect(result.current).toEqual(defaultValue)
|
|
})
|
|
})
|
|
|
|
describe('Context without name', () => {
|
|
/**
|
|
* Test that contexts can be created without a name
|
|
* The error message should use a generic fallback
|
|
*/
|
|
it('should create context without name and show generic error', () => {
|
|
type TestContextValue = { value: string }
|
|
const [, useTestContext] = createCtx<TestContextValue>()
|
|
|
|
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => { /* suppress error */ })
|
|
|
|
expect(() => {
|
|
renderHook(() => useTestContext())
|
|
}).toThrow('No related context found.')
|
|
|
|
consoleError.mockRestore()
|
|
})
|
|
})
|
|
})
|