import type { AccountSettingTab } from '../constants' import type { ConsoleStateFixture } from '@/test/console/state-fixture' import { fireEvent, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useState } from 'react' import { baseProviderContextValue, useProviderContext } from '@/context/provider-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { renderWithConsoleQuery } from '@/test/console/query-data' import { ACCOUNT_SETTING_TAB } from '../constants' import AccountSetting from '../index' const mockResetModelProviderListExpanded = vi.fn() const mockConfig = vi.hoisted(() => ({ IS_CLOUD_EDITION: true, })) const mockConsoleState = vi.hoisted(() => ({ current: null as unknown, })) vi.mock('@/config', async (importOriginal) => { const actual = await importOriginal() return { ...actual, get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION }, } }) vi.mock('@/context/provider-context', async (importOriginal) => { const actual = await importOriginal() return { ...actual, useProviderContext: vi.fn(), } }) vi.mock('@/context/account-state', async () => { const { createAccountStateModuleMock } = await import('@/test/console/state-fixture') return createAccountStateModuleMock(() => mockConsoleState.current ?? {}) }) vi.mock('@/context/workspace-state', async () => { const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') return createWorkspaceStateModuleMock(() => mockConsoleState.current ?? {}) }) vi.mock('@/context/permission-state', async () => { const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture') return createPermissionStateModuleMock(() => mockConsoleState.current ?? {}) }) vi.mock('@/context/version-state', async () => { const { createVersionStateModuleMock } = await import('@/test/console/state-fixture') return createVersionStateModuleMock(() => mockConsoleState.current ?? {}) }) 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('next-themes', () => ({ useTheme: vi.fn(() => ({ theme: 'system', setTheme: vi.fn(), })), })) vi.mock('@/app/components/header/account-setting/model-provider-page/atoms', () => ({ useResetModelProviderListExpanded: () => mockResetModelProviderListExpanded, })) vi.mock('@/app/components/header/account-setting/model-provider-page', () => ({ default: ({ onSearchTextChange, searchText, }: { onSearchTextChange?: (value: string) => void searchText: string }) => ( onSearchTextChange?.(event.target.value)} /> ), })) vi.mock('@/service/use-datasource', () => ({ useGetDataSourceListAuth: vi.fn(() => ({ data: { result: [] } })), })) vi.mock('@/service/use-common', async (importOriginal) => { const actual = await importOriginal() return { ...actual, useMembers: vi.fn(() => ({ data: { accounts: [] }, refetch: vi.fn() })), useProviderContext: vi.fn(), } }) vi.mock('@/service/client', async (importOriginal) => { const actual = await importOriginal() return { ...actual, consoleQuery: new Proxy(actual.consoleQuery, { get(target, prop, receiver) { if (prop === 'apiBasedExtension') { return { get: { queryOptions: () => ({ queryKey: ['console', 'api-based-extension'], queryFn: () => Promise.resolve([]), }), }, } } return Reflect.get(target, prop, receiver) }, }), } }) vi.mock('@/app/components/billing/billing-page', () => ({ __esModule: true, default: () =>
, })) vi.mock('@/app/components/header/account-setting/data-source-page-new', () => ({ __esModule: true, default: () =>
, })) vi.mock('@/app/components/header/account-setting/permissions-page', () => ({ __esModule: true, default: () =>
, })) vi.mock('@/app/components/header/account-setting/access-rules-page', () => ({ __esModule: true, default: () =>
, })) const baseConsoleState: ConsoleStateFixture = { userProfile: { id: '1', name: 'Test User', email: 'test@example.com', avatar: '', avatar_url: '', is_password_set: false, }, refreshUserProfile: 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, refreshCurrentWorkspace: 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, }, isLoadingCurrentWorkspace: false, workspacePermissionKeys: [ 'workspace.member.manage', 'workspace.role.manage', 'data_source.manage', 'api_extension.manage', 'customization.manage', ], } describe('AccountSetting', () => { const mockOnCancel = vi.fn() const mockOnTabChange = vi.fn() const renderAccountSetting = (props?: { initialTab?: AccountSettingTab onCancel?: () => void onTabChange?: (tab: AccountSettingTab) => void rbacEnabled?: boolean }) => { const { initialTab = ACCOUNT_SETTING_TAB.MEMBERS, onCancel = mockOnCancel, onTabChange = mockOnTabChange, rbacEnabled = true, } = props ?? {} const StatefulAccountSetting = () => { const [activeTab, setActiveTab] = useState(initialTab) return ( { setActiveTab(tab) onTabChange(tab) }} /> ) } return renderWithConsoleQuery(, { systemFeatures: { webapp_auth: { enabled: true }, branding: { enabled: false }, enable_marketplace: true, enable_collaboration_mode: false, rbac_enabled: rbacEnabled, }, }) } beforeEach(() => { vi.clearAllMocks() mockConfig.IS_CLOUD_EDITION = true vi.mocked(useProviderContext).mockReturnValue({ ...baseProviderContextValue, enableBilling: true, enableReplaceWebAppLogo: true, }) mockConsoleState.current = baseConsoleState vi.mocked(useBreakpoints).mockReturnValue(MediaType.pc) }) describe('Rendering', () => { it('should render the sidebar with correct menu items', () => { // Act renderAccountSetting() // Assert expect(screen.getByText('common.settings.settings'))!.toBeInTheDocument() expect(screen.getAllByText('common.settings.workspace').length).toBeGreaterThan(0) expect(screen.queryByText('common.settings.provider'))!.not.toBeInTheDocument() expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(0) expect( screen.getByRole('button', { name: 'common.settings.rolesAndPermissions' }), ).toBeInTheDocument() expect( screen.getByRole('button', { name: 'common.settings.permissionSet' }), ).toBeInTheDocument() expect(screen.getByText('common.settings.billing'))!.toBeInTheDocument() expect(screen.getByRole('button', { name: 'appLog.archives.title' })).toBeInTheDocument() expect(screen.queryByText('common.settings.dataSource'))!.not.toBeInTheDocument() expect(screen.queryByText('common.settings.customEndpoint'))!.not.toBeInTheDocument() expect(screen.getByText('custom.custom'))!.toBeInTheDocument() expect( screen .getByRole('button', { name: 'custom.custom' }) .compareDocumentPosition(screen.getByRole('button', { name: 'appLog.archives.title' })), ).toBe(Node.DOCUMENT_POSITION_FOLLOWING) expect(screen.getByText('common.settings.preferences'))!.toBeInTheDocument() }) it('should keep hidden legacy tab metadata for direct entries', () => { // Act renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.DATA_SOURCE }) // Assert expect(screen.getByText('common.settings.dataSource'))!.toBeInTheDocument() }) it('should normalize legacy language tab entries to preferences', () => { // Act renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.LANGUAGE }) // Assert const preferencesButton = screen.getByRole('button', { name: 'common.settings.preferences' }) expect(preferencesButton.querySelector('.i-ri-equalizer-2-fill')).toBeInTheDocument() expect(screen.getByText('common.account.general')).toBeInTheDocument() expect(screen.getByText('common.account.appearanceLabel')).toBeInTheDocument() }) it('should hide sidebar labels on mobile', () => { // Arrange vi.mocked(useBreakpoints).mockReturnValue(MediaType.mobile) // Act renderAccountSetting() // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation // Assert // On mobile, the labels should not be rendered as per the implementation expect(screen.queryByText('common.settings.provider')).not.toBeInTheDocument() }) it('should hide billing from dataset operators', () => { // Arrange const datasetOperatorContext = { ...baseConsoleState, isCurrentWorkspaceDatasetOperator: true, } mockConsoleState.current = datasetOperatorContext // Act renderAccountSetting() // Assert expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument() expect( screen.getByRole('button', { name: 'common.settings.rolesAndPermissions' }), ).toBeInTheDocument() expect( screen.getByRole('button', { name: 'common.settings.permissionSet' }), ).toBeInTheDocument() expect( screen.queryByRole('button', { name: 'common.settings.billing' }), ).not.toBeInTheDocument() expect(screen.getByRole('button', { name: 'appLog.archives.title' })).toBeInTheDocument() expect(screen.getByRole('button', { name: 'custom.custom' })).toBeInTheDocument() expect( screen.getByRole('button', { name: 'common.settings.preferences' }), ).toBeInTheDocument() }) it('should keep moved integrations hidden when api extension permission is missing', () => { // Arrange const contextWithoutApiExtensionPermission = { ...baseConsoleState, workspacePermissionKeys: baseConsoleState.workspacePermissionKeys!.filter( (key) => key !== 'api_extension.manage', ), } mockConsoleState.current = contextWithoutApiExtensionPermission // Act renderAccountSetting() // Assert expect(screen.queryByText('common.settings.provider')).not.toBeInTheDocument() expect(screen.queryByText('common.settings.dataSource')).not.toBeInTheDocument() expect(screen.queryByText('common.settings.customEndpoint')).not.toBeInTheDocument() }) it('should show custom tab when customization permission is missing', () => { // Arrange const contextWithoutCustomizationPermission = { ...baseConsoleState, workspacePermissionKeys: baseConsoleState.workspacePermissionKeys!.filter( (key) => key !== 'customization.manage', ), } mockConsoleState.current = contextWithoutCustomizationPermission // Act renderAccountSetting() // Assert expect(screen.queryByText('common.settings.provider')).not.toBeInTheDocument() expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument() expect(screen.getByRole('button', { name: 'appLog.archives.title' })).toBeInTheDocument() expect(screen.getByRole('button', { name: 'custom.custom' })).toBeInTheDocument() expect(screen.getByText('common.settings.preferences'))!.toBeInTheDocument() }) it('should hide role and permission set entries when role management permission is missing', () => { // Arrange const contextWithoutRoleManagePermission = { ...baseConsoleState, workspacePermissionKeys: baseConsoleState.workspacePermissionKeys!.filter( (key) => key !== 'workspace.role.manage', ), } mockConsoleState.current = contextWithoutRoleManagePermission // Act renderAccountSetting() // Assert expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument() expect( screen.queryByRole('button', { name: 'common.settings.rolesAndPermissions' }), ).not.toBeInTheDocument() expect( screen.queryByRole('button', { name: 'common.settings.permissionSet' }), ).not.toBeInTheDocument() }) it('should hide role and permission set entries when RBAC is disabled', () => { // Act renderAccountSetting({ rbacEnabled: false }) // Assert expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument() expect( screen.queryByRole('button', { name: 'common.settings.rolesAndPermissions' }), ).not.toBeInTheDocument() expect( screen.queryByRole('button', { name: 'common.settings.permissionSet' }), ).not.toBeInTheDocument() }) it('should not render direct role pages when RBAC is disabled', () => { // Act renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.PERMISSION_SET, rbacEnabled: false }) // Assert expect(screen.queryByTestId('access-rules-page')).not.toBeInTheDocument() expect(screen.queryByTestId('permissions-page')).not.toBeInTheDocument() expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(0) }) it('should hide billing and custom tabs when disabled', () => { // Arrange vi.mocked(useProviderContext).mockReturnValue({ ...baseProviderContextValue, enableBilling: false, enableReplaceWebAppLogo: false, }) // Act renderAccountSetting() // Assert // Assert expect(screen.queryByText('common.settings.billing')).not.toBeInTheDocument() expect(screen.queryByText('custom.custom')).not.toBeInTheDocument() }) it('should show billing to regular members without billing permission keys', () => { // Arrange const regularMemberContext = { ...baseConsoleState, currentWorkspace: { ...baseConsoleState.currentWorkspace, role: 'normal' as const, }, isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, isCurrentWorkspaceDatasetOperator: false, workspacePermissionKeys: [], } mockConsoleState.current = regularMemberContext // Act renderAccountSetting() // Assert expect(screen.getByRole('button', { name: 'common.settings.billing' })).toBeInTheDocument() }) it('should hide workflow log archives outside cloud edition', () => { // Arrange mockConfig.IS_CLOUD_EDITION = false // Act renderAccountSetting() // Assert expect( screen.queryByRole('button', { name: 'appLog.archives.title' }), ).not.toBeInTheDocument() }) it('should hide workflow log archives from custom RBAC roles that are not owner or admin', () => { // Arrange const contextWithRoleManagePermissionButNotManager = { ...baseConsoleState, currentWorkspace: { ...baseConsoleState.currentWorkspace, role: 'normal' as const, }, isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, workspacePermissionKeys: [ ...(baseConsoleState.workspacePermissionKeys ?? []), 'workspace.role.manage', ], } mockConsoleState.current = contextWithRoleManagePermissionButNotManager // Act renderAccountSetting() // Assert expect( screen.queryByRole('button', { name: 'appLog.archives.title' }), ).not.toBeInTheDocument() }) it('should not render workflow log archives page outside cloud edition', () => { // Arrange mockConfig.IS_CLOUD_EDITION = false // Act renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.WORKFLOW_LOG_ARCHIVES }) // Assert expect(screen.queryByText('appLog.archives.upgradeTip.title')).not.toBeInTheDocument() expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(0) }) it('should render a direct billing entry for regular members without billing permission keys', () => { // Arrange const regularMemberContext = { ...baseConsoleState, currentWorkspace: { ...baseConsoleState.currentWorkspace, role: 'normal' as const, }, isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, isCurrentWorkspaceDatasetOperator: false, workspacePermissionKeys: [], } mockConsoleState.current = regularMemberContext // Act renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.BILLING }) // Assert expect(screen.getByTestId('billing-page')).toBeInTheDocument() }) it('should not render a direct billing entry for dataset operators', () => { mockConsoleState.current = { ...baseConsoleState, isCurrentWorkspaceDatasetOperator: true, } renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.BILLING }) expect(screen.queryByTestId('billing-page')).not.toBeInTheDocument() }) }) describe('Tab Navigation', () => { it('should change active tab when clicking on menu item', () => { // Arrange renderAccountSetting({ onTabChange: mockOnTabChange }) // Act fireEvent.click(screen.getByText('common.settings.billing')) // Assert expect(mockOnTabChange).toHaveBeenCalledWith(ACCOUNT_SETTING_TAB.BILLING) expect(screen.getAllByText('common.settings.billing').length).toBeGreaterThan(1) }) it('should navigate through various tabs and show correct details', () => { // Act & Assert renderAccountSetting() // 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) // Custom fireEvent.click(screen.getByText('custom.custom')) // Custom Page uses 'custom.custom' key as well. expect(screen.getAllByText('custom.custom').length).toBeGreaterThan(1) // Workflow Log Archives fireEvent.click(screen.getByRole('button', { name: 'appLog.archives.title' })) expect(screen.getByText('appLog.archives.upgradeTip.title')).toBeInTheDocument() // Members fireEvent.click(screen.getAllByText('common.settings.members')[0]!) expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(1) // Roles & Permissions fireEvent.click(screen.getByRole('button', { name: 'common.settings.rolesAndPermissions' })) expect(screen.getByTestId('permissions-page')).toBeInTheDocument() // Permission Set fireEvent.click(screen.getByRole('button', { name: 'common.settings.permissionSet' })) expect(screen.getByText('common.settings.permissionSetDescription')).toBeInTheDocument() expect(screen.getByTestId('access-rules-page')).toBeInTheDocument() // Language fireEvent.click(screen.getByText('common.settings.preferences')) expect(screen.getByText('common.account.general')).toBeInTheDocument() expect(screen.getByText('common.account.appearanceLabel')).toBeInTheDocument() }) it('should switch the preferences icon when the tab is active', () => { renderAccountSetting() const preferencesButton = screen.getByRole('button', { name: 'common.settings.preferences' }) expect(preferencesButton.querySelector('.i-ri-equalizer-2-line')).toBeInTheDocument() fireEvent.click(preferencesButton) expect(preferencesButton.querySelector('.i-ri-equalizer-2-fill')).toBeInTheDocument() }) }) describe('Interactions', () => { it('should call onCancel when clicking close button', () => { // Act renderAccountSetting() const closeIcon = document.querySelector('.i-ri-close-line') const closeButton = closeIcon?.closest('button') expect(closeButton).not.toBeNull() fireEvent.click(closeButton!) // Assert expect(mockOnCancel).toHaveBeenCalled() }) it('should call onCancel when pressing Escape key', () => { // Act renderAccountSetting() fireEvent.keyDown(document, { key: 'Escape' }) // Assert expect(mockOnCancel).toHaveBeenCalled() }) it('should keep provider search controlled at the account setting boundary', async () => { // Arrange const user = userEvent.setup() renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.PROVIDER }) // Act const input = screen.getByRole('searchbox', { name: 'common.operation.search' }) await user.type(input, 'test-search') // Assert expect(input)!.toHaveValue('test-search') }) it('should handle scroll event in panel', () => { // Act renderAccountSetting() const scrollContainer = screen.getByRole('dialog').querySelector('.overscroll-contain') // Assert // Assert expect(scrollContainer)!.toBeInTheDocument() if (scrollContainer) { // Scroll down fireEvent.scroll(scrollContainer, { target: { scrollTop: 100 } }) expect(scrollContainer)!.toHaveClass('overscroll-contain') // Scroll back up fireEvent.scroll(scrollContainer, { target: { scrollTop: 0 } }) } }) }) })