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/hooks.spec.ts new file mode 100644 index 0000000000..4d6941ddc6 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/hooks.spec.ts @@ -0,0 +1,90 @@ +import { renderHook } from '@testing-library/react' +import { useLanguage } from './hooks' +import { useContext } from 'use-context-selector' +import { after } from 'node:test' + +jest.mock('swr', () => ({ + __esModule: true, + default: jest.fn(), // mock useSWR + useSWRConfig: jest.fn(), +})) + +// mock use-context-selector +jest.mock('use-context-selector', () => ({ + useContext: jest.fn(), +})) + +// mock service/common functions +jest.mock('@/service/common', () => ({ + fetchDefaultModal: jest.fn(), + fetchModelList: jest.fn(), + fetchModelProviderCredentials: jest.fn(), + fetchModelProviders: jest.fn(), + getPayUrl: jest.fn(), +})) + +// mock context hooks +jest.mock('@/context/i18n', () => ({ + __esModule: true, + default: jest.fn(), +})) + +jest.mock('@/context/provider-context', () => ({ + useProviderContext: jest.fn(), +})) + +jest.mock('@/context/modal-context', () => ({ + useModalContextSelector: jest.fn(), +})) + +jest.mock('@/context/event-emitter', () => ({ + useEventEmitterContextContext: jest.fn(), +})) + +// mock plugins +jest.mock('@/app/components/plugins/marketplace/hooks', () => ({ + useMarketplacePlugins: jest.fn(), +})) + +jest.mock('@/app/components/plugins/marketplace/utils', () => ({ + getMarketplacePluginsByCollectionId: jest.fn(), +})) + +jest.mock('./provider-added-card', () => { + // eslint-disable-next-line no-labels, ts/no-unused-expressions + UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST: [] +}) + +after(() => { + jest.resetModules() + jest.clearAllMocks() +}) + +describe('useLanguage', () => { + it('should replace hyphen with underscore in locale', () => { + (useContext as jest.Mock).mockReturnValue({ + locale: 'en-US', + }) + const { result } = renderHook(() => useLanguage()) + expect(result.current).toBe('en_US') + }) + + it('should return locale as is if no hyphen exists', () => { + (useContext as jest.Mock).mockReturnValue({ + locale: 'enUS', + }) + + const { result } = renderHook(() => useLanguage()) + expect(result.current).toBe('enUS') + }) + + it('should handle multiple hyphens', () => { + // Mock the I18n context return value + (useContext as jest.Mock).mockReturnValue({ + locale: 'zh-Hans-CN', + }) + + const { result } = renderHook(() => useLanguage()) + expect(result.current).toBe('zh_Hans-CN') + }) +}) diff --git a/web/hooks/use-breakpoints.spec.ts b/web/hooks/use-breakpoints.spec.ts new file mode 100644 index 0000000000..315e514f0f --- /dev/null +++ b/web/hooks/use-breakpoints.spec.ts @@ -0,0 +1,93 @@ +import { act, renderHook } from '@testing-library/react' +import useBreakpoints, { MediaType } from './use-breakpoints' + +describe('useBreakpoints', () => { + const originalInnerWidth = window.innerWidth + + // Mock the window resize event + const fireResize = (width: number) => { + window.innerWidth = width + act(() => { + window.dispatchEvent(new Event('resize')) + }) + } + + // Restore the original innerWidth after tests + afterAll(() => { + window.innerWidth = originalInnerWidth + }) + + it('should return mobile for width <= 640px', () => { + // Mock window.innerWidth for mobile + Object.defineProperty(window, 'innerWidth', { + writable: true, + configurable: true, + value: 640, + }) + + const { result } = renderHook(() => useBreakpoints()) + expect(result.current).toBe(MediaType.mobile) + }) + + it('should return tablet for width > 640px and <= 768px', () => { + // Mock window.innerWidth for tablet + Object.defineProperty(window, 'innerWidth', { + writable: true, + configurable: true, + value: 768, + }) + + const { result } = renderHook(() => useBreakpoints()) + expect(result.current).toBe(MediaType.tablet) + }) + + it('should return pc for width > 768px', () => { + // Mock window.innerWidth for pc + Object.defineProperty(window, 'innerWidth', { + writable: true, + configurable: true, + value: 1024, + }) + + const { result } = renderHook(() => useBreakpoints()) + expect(result.current).toBe(MediaType.pc) + }) + + it('should update media type when window resizes', () => { + // Start with desktop + Object.defineProperty(window, 'innerWidth', { + writable: true, + configurable: true, + value: 1024, + }) + + const { result } = renderHook(() => useBreakpoints()) + expect(result.current).toBe(MediaType.pc) + + // Resize to tablet + fireResize(768) + expect(result.current).toBe(MediaType.tablet) + + // Resize to mobile + fireResize(600) + expect(result.current).toBe(MediaType.mobile) + }) + + it('should clean up event listeners on unmount', () => { + // Spy on addEventListener and removeEventListener + const addEventListenerSpy = jest.spyOn(window, 'addEventListener') + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener') + + const { unmount } = renderHook(() => useBreakpoints()) + + // Unmount should trigger cleanup + unmount() + + expect(addEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function)) + expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function)) + + // Clean up spies + addEventListenerSpy.mockRestore() + removeEventListenerSpy.mockRestore() + }) +}) diff --git a/web/jest.config.ts b/web/jest.config.ts index aa2f22bf82..9164734d64 100644 --- a/web/jest.config.ts +++ b/web/jest.config.ts @@ -98,7 +98,7 @@ const config: Config = { // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module moduleNameMapper: { - '^@/components/(.*)$': '/components/$1', + '^@/(.*)$': '/$1', '^lodash-es$': 'lodash', }, @@ -133,7 +133,7 @@ const config: Config = { // restoreMocks: false, // The root directory that Jest should scan for tests and modules within - // rootDir: undefined, + rootDir: './', // A list of paths to directories that Jest should use to search for files in // roots: [