test: sweep per-file UI mocks, lift shared mocks to global setup, prebundle @lobehub/ui (#18761)

*  test: sweep per-file base-ui mocks now real components render

101 test files audited: 28 mocks deleted outright, 10 switched to the
canonical composed stubs, 42 trimmed to only the assertion-bound entries
over an importOriginal spread, 21 left as-is (closed antd-style factories
or already minimal). toast.loading stub now returns a close/update handle.

Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ

*  test: sweep remaining ui/antd mocks, lift zustand/i18n mocks to global setup, prebundle @lobehub/ui

- delete closed @lobehub/ui, base-ui, antd, antd-style render stubs across 150+ test files; keep only assertion-bound spies composed over importOriginal
- register zustand/traditional and react-i18next key-passthrough (defaultValue-aware) mocks once in tests/setup.ts; drop 51 + 94 identical per-file copies
- drop 18 usePermission mocks whose real implementation already returns allowed:true
- prebundle @lobehub/ui (all subpaths) and motion via deps.optimizer so workers share one chunk instead of re-evaluating the ESM graph per file (585s -> 383s)
- fix tests exposed by store auto-reset / prebundling: displayMessage leaked historyCount, copyToClipboard spyOn on ESM namespace, ApiKey defaultValue assertions, lazy executor timeout

Claude-Session: https://claude.ai/code/session_01EjmRHEnEMf5mEDRFCDAjy1

* 🐛 fix(test): widen registerBeforeApprove callback type in webOnboarding test

Claude-Session: https://claude.ai/code/session_01SoctQBoW5DDXH1brMcJ6yd

* 🐛 fix(test): raise timeout for channel discard test rendering real form

Claude-Session: https://claude.ai/code/session_01SoctQBoW5DDXH1brMcJ6yd
This commit is contained in:
Innei
2026-08-28 23:52:00 +08:00
committed by GitHub
parent 230406da66
commit 431f6371af
257 changed files with 687 additions and 3616 deletions
+1 -12
View File
@@ -1,12 +1 @@
{
"src/features/Conversation/Error/index.test.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/store/chat/slices/message/action.test.ts": {
"no-restricted-imports": {
"count": 1
}
}
}
{}
@@ -24,20 +24,15 @@ vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key.split('.').at(-1) || key }),
}));
// Headless stubs for @lobehub/ui so we exercise our own markup, not the design
// system internals (which would need a theme provider in jsdom).
vi.mock('@lobehub/ui', () => ({
ActionIcon: ({ title }: { title?: string }) => <button data-testid="action-icon" title={title} />,
Block: ({ children, onClick }: { children?: ReactNode; onClick?: () => void }) => (
<div data-testid="block" onClick={onClick}>
// base-ui Button requires the app-level motion provider (this package has no
// shared vitest setup, unlike src tests which stub it globally).
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
Button: ({ children, ...props }: { children?: ReactNode }) => (
<button type="button" {...props}>
{children}
</div>
</button>
),
Icon: () => <span data-testid="icon" />,
Markdown: ({ children }: { children?: ReactNode }) => (
<div data-testid="markdown">{children}</div>
),
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
}));
vi.mock('@/features/AgentTasks/features/AssigneeAvatar', () => ({
@@ -111,7 +106,7 @@ describe('EditTaskRender', () => {
it('renders the instruction preview as markdown', () => {
renderEdit({ instruction: '# Do the thing' });
expect(screen.getByTestId('markdown').textContent).toContain('# Do the thing');
expect(screen.getByRole('heading', { name: 'Do the thing' })).toBeTruthy();
});
it('renders dependency changes', () => {
@@ -125,7 +120,7 @@ describe('EditTaskRender', () => {
renderEdit({});
expect(screen.getByText('T-1')).toBeTruthy();
expect(screen.queryByTestId('markdown')).toBeNull();
expect(screen.queryByRole('heading')).toBeNull();
expect(screen.queryByTestId('assignee-avatar')).toBeNull();
});
});
@@ -166,7 +161,7 @@ describe('SetTaskVerifyRender', () => {
});
// Body is just the requirement markdown — verifier / iterations are not shown.
expect(screen.getByTestId('markdown').textContent).toContain('## Acceptance');
expect(screen.getByRole('heading', { name: 'Acceptance' })).toBeTruthy();
expect(screen.queryByTestId('assignee-avatar')).toBeNull();
expect(screen.queryByText('3')).toBeNull();
});
+21 -4
View File
@@ -1,18 +1,35 @@
import { dirname, resolve } from 'node:path';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vitest/config';
const packageDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(packageDir, '../..');
const packageDir = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(packageDir, '../..');
export default defineConfig({
plugins: [
{
enforce: 'pre',
name: 'stub-lobehub-ui-motion-provider',
resolveId(id, importer) {
if (!importer || !importer.includes('/@lobehub/ui/')) return null;
if (/MotionProvider(?:\/index(?:\.(?:mjs|js|tsx))?)?$/.test(id))
return path.resolve(repoRoot, 'tests/mocks/lobehubUiMotionProvider.tsx');
return null;
},
},
],
resolve: {
alias: {
'@': resolve(repoRoot, 'src'),
'@': path.resolve(repoRoot, 'src'),
},
},
test: {
environment: 'happy-dom',
server: {
deps: {
inline: [/@lobehub\//],
},
},
},
});
@@ -19,10 +19,7 @@ vi.mock('@lobechat/shared-tool-ui/styles', () => ({
vi.mock('@lobehub/ui', () => ({}));
vi.mock('antd-style', () => ({
createStaticStyles: () => new Proxy({}, { get: (_target, property) => String(property) }),
cx: (...classNames: Array<string | false | undefined>) => classNames.filter(Boolean).join(' '),
}));
vi.mock('@lobehub/ui/base-ui', () => ({}));
describe('AskUserQuestionInspector', () => {
afterEach(cleanup);
@@ -17,22 +17,11 @@ vi.mock('@lobechat/shared-tool-ui/styles', () => ({
shinyTextStyles: { shinyText: 'shiny-text' },
}));
vi.mock('@lobehub/ui', () => ({
Icon: ({ className }: { className?: string }) => (
<span className={className} data-testid="icon" />
),
}));
vi.mock('antd-style', () => ({
createStaticStyles: () => new Proxy({}, { get: (_target, property) => String(property) }),
cx: (...classNames: Array<string | false | undefined>) => classNames.filter(Boolean).join(' '),
}));
describe('Codex ErrorInspector', () => {
afterEach(cleanup);
it('renders the warning icon and error message', () => {
render(
const { container } = render(
<ErrorInspector
apiName="error"
args={{ id: 'item_0', message: 'The session model changed.', type: 'error' }}
@@ -40,7 +29,7 @@ describe('Codex ErrorInspector', () => {
/>,
);
expect(screen.getByTestId('icon')).toBeTruthy();
expect(container.querySelector('svg')).toBeTruthy();
expect(screen.getByText('The session model changed.')).toBeTruthy();
});
+5
View File
@@ -3,5 +3,10 @@ import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
server: {
deps: {
inline: [/@lobehub\//],
},
},
},
});
@@ -11,7 +11,8 @@ const mockUseApp = {
notification: { open: vi.fn() },
};
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: {
useApp: vi.fn(() => mockUseApp),
},
@@ -3,15 +3,6 @@ import { describe, expect, it, vi } from 'vitest';
import AsyncBoundary from './index';
// Stub the base-ui Button (the failure state's Retry) to a native button — it
// needs a MotionProvider the app sets up globally but the unit env doesn't; the
// state-machine assertions only care that a button is/isn't present. vitest
// hoists this above the imports regardless of position.
vi.mock('@lobehub/ui/base-ui', () => ({
Button: ({ children, onClick }: any) => <button onClick={onClick}>{children}</button>,
Text: ({ children }: any) => <span>{children}</span>,
}));
const DATA = <div>DATA_CONTENT</div>;
const EMPTY = <div>EMPTY_ONBOARDING</div>;
const LOADING = <div>LOADING_SKELETON</div>;
@@ -12,9 +12,10 @@ import { getContainer, useDragUpload } from './useDragUpload';
// Mock the hooks and components
vi.mock('@/hooks/useMediaUploadAbility');
vi.mock('@/store/agent');
vi.mock('@lobehub/ui/base-ui', () => {
return { toast: { warning: vi.fn() } };
});
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
...(await import('~base-ui-stubs')).baseUiStubs,
}));
describe('useDragUpload', () => {
let mockOnUploadFiles: Mock;
@@ -4,7 +4,8 @@ import { remoteServerErrorToast } from './remoteServerErrorToast';
const toastError = vi.fn();
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
toast: { error: (...args: unknown[]) => toastError(...args) },
}));
+23 -37
View File
@@ -1,46 +1,29 @@
import type { BlockProps } from '@lobehub/ui';
import { render, screen } from '@testing-library/react';
import { type ComponentProps, type ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import type { ComponentType } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import StatisticCard from './index';
const blockPropsSpy = vi.hoisted(() => vi.fn());
vi.mock('@lobehub/ui', async (importOriginal) => {
const actual = await importOriginal<Record<string, unknown>>();
const ActualBlock = actual.Block as ComponentType<BlockProps>;
return {
...actual,
Block: ({
children,
className,
padding,
paddingBlock,
paddingInline,
style,
variant,
}: {
children?: ReactNode;
className?: string;
padding?: number | string;
paddingBlock?: number | string;
paddingInline?: number | string;
style?: ComponentProps<'div'>['style'];
variant?: string;
}) => (
<div
className={className}
data-padding={padding}
data-padding-block={paddingBlock}
data-padding-inline={paddingInline}
data-testid="block"
data-variant={variant}
style={style}
>
{children}
</div>
),
Block: (props: BlockProps) => {
blockPropsSpy(props);
return <ActualBlock {...props} />;
},
};
});
describe('StatisticCard', () => {
beforeEach(() => {
blockPropsSpy.mockClear();
});
it('renders title and formatted value with prefix, suffix and precision', () => {
render(
<StatisticCard
@@ -115,16 +98,19 @@ describe('StatisticCard', () => {
/>,
);
const block = screen.getByTestId('block');
expect(block).toHaveAttribute('data-variant', 'outlined');
expect(block).toHaveAttribute('data-padding', '24');
expect(block).toHaveAttribute('data-padding-block', '8');
expect(block).toHaveAttribute('data-padding-inline', '16');
expect(blockPropsSpy).toHaveBeenCalledWith(
expect.objectContaining({
padding: 24,
paddingBlock: 8,
paddingInline: 16,
variant: 'outlined',
}),
);
});
it('defaults to the borderless variant', () => {
render(<StatisticCard title="T" />);
expect(screen.getByTestId('block')).toHaveAttribute('data-variant', 'borderless');
expect(blockPropsSpy).toHaveBeenCalledWith(expect.objectContaining({ variant: 'borderless' }));
});
});
@@ -11,16 +11,9 @@ vi.mock('@lobehub/ui', () => ({
TextArea: () => null,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
Button: () => null,
Select: () => null,
Switch: () => null,
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
createModal: mocks.createModal,
useModalContext: () => ({ close: vi.fn() }),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
describe('openCriterionEditModal', () => {
@@ -2,20 +2,17 @@
* @vitest-environment happy-dom
*/
import { fireEvent, render } from '@testing-library/react';
import { createElement, type ReactNode } from 'react';
import { createElement } from 'react';
import { describe, expect, it, vi } from 'vitest';
import TopicPanel from './TopicPanel';
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children }: { children?: ReactNode }) => createElement('div', null, children),
Icon: () => null,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
// Real base-ui ActionIcon only surfaces its title via a hover Tooltip, so the
// static DOM has no accessible name to query.
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
ActionIcon: ({ onClick, title }: { onClick?: () => void; title?: string }) =>
createElement('button', { onClick, title }, title),
Text: ({ children }: { children?: ReactNode }) => createElement('span', null, children),
}));
vi.mock('@/features/AgentTasks/AgentTaskDetail/TopicChatDrawer', () => ({
@@ -41,10 +38,6 @@ vi.mock('@/features/AgentTasks/AgentTaskDetail/TopicChatDrawer', () => ({
),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
describe('TopicPanel', () => {
it('renders the topic conversation in the right rail and returns to runs', () => {
const onBack = vi.fn();
@@ -4,25 +4,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
import { AttachmentThumbs } from './attachments';
// The unit env has neither the MotionProvider nor the image-preview portal the real
// components need; stub them down to the DOM the assertions actually read.
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children, onClick }: any) => <div onClick={onClick}>{children}</div>,
Icon: () => null,
Image: ({ alt, src }: any) => <img alt={alt} src={src} />,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
Button: ({ children, onClick }: any) => (
<button type="button" onClick={onClick}>
{children}
</button>
),
toast: { error: vi.fn(), success: vi.fn() },
}));
vi.mock('antd', () => ({ Upload: ({ children }: any) => <div>{children}</div> }));
vi.mock('@/store/file', () => ({ useFileStore: () => vi.fn() }));
const attachments = [{ id: 'att-1', name: 'screenshot.png', url: 'https://example.com/a.png' }];
@@ -1,5 +1,4 @@
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { MemoryRouter } from 'react-router';
import { beforeEach, describe, expect, it, vi } from 'vitest';
@@ -16,26 +15,6 @@ const mocks = vi.hoisted(() => ({
},
}));
vi.mock('@lobehub/ui', () => ({
Icon: () => <span />,
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
}));
vi.mock('antd', async (importOriginal) => {
const actual = (await importOriginal()) as Record<string, unknown>;
return {
...actual,
Breadcrumb: ({ items }: { items: Array<{ title: ReactNode }> }) => (
<nav>
{items.map((item, index) => (
<span key={index}>{item.title}</span>
))}
</nav>
),
};
});
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => (key === 'inbox.title' ? 'Lobe AI' : key),
@@ -9,12 +9,10 @@ import TopicSelector from './TopicSelector';
const switchTopic = vi.fn();
const useFetchTopics = vi.fn();
vi.mock('@lobehub/ui', () => ({
DropdownMenu: ({ children }: any) => <div>{children}</div>,
Flexbox: ({ children }: any) => <div>{children}</div>,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
// Real base-ui ActionIcon only surfaces its title via a hover Tooltip, so the
// static DOM has no accessible name to query.
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
ActionIcon: ({ disabled, onClick, title }: any) => (
<button disabled={disabled} type="button" onClick={onClick}>
{title}
@@ -22,13 +20,6 @@ vi.mock('@lobehub/ui/base-ui', () => ({
),
}));
vi.mock('antd-style', () => ({
createStaticStyles: () => ({
time: 'time',
title: 'title',
}),
}));
vi.mock('dayjs', () => {
const dayjs = () => ({
diff: () => 0,
@@ -38,10 +29,6 @@ vi.mock('dayjs', () => {
return { default: dayjs };
});
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@/const/layoutTokens', () => ({
DESKTOP_HEADER_ICON_SMALL_SIZE: 24,
}));
@@ -13,12 +13,6 @@ vi.mock('react-router', () => ({
useParams: () => ({ aid: 'agent-from-url' }),
}));
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) => (
<div {...(props as Record<string, unknown>)}>{children}</div>
),
}));
const pageEditorProps = vi.hoisted(() => ({
current: undefined as undefined | Record<string, unknown>,
}));
@@ -22,29 +22,6 @@ const modalConfirm = vi.hoisted(() => vi.fn());
const openDocumentMock = vi.hoisted(() => vi.fn());
const removeDocumentMock = vi.hoisted(() => vi.fn());
vi.mock('@lobehub/ui', () => ({
ActionIcon: ({
icon,
onClick,
title,
}: {
icon?: { displayName?: string; name?: string };
onClick?: () => void;
title?: string;
}) => (
<button aria-label={title} data-icon={icon?.displayName ?? icon?.name} onClick={onClick}>
{title}
</button>
),
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Icon: () => <span />,
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
}));
vi.mock('@lobehub/ui/icons', () => ({
SkillsIcon: () => null,
}));
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...((await importOriginal()) as Record<string, unknown>),
ActionIcon: ({
@@ -79,7 +56,8 @@ vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
),
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: {
useApp: () => ({
message: { error: messageError, success: messageSuccess, warning: messageWarning },
@@ -98,12 +76,6 @@ vi.mock('@/features/Workspace/useWorkspaceAwareNavigate', () => ({
useWorkspaceAwareNavigate: () => navigateMock,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@/features/ExplorerTree', () => {
interface MockExplorerTreeProps {
canDrag?: (node: ExplorerTreeNode<unknown>) => boolean;
@@ -16,10 +16,6 @@ vi.mock('@/store/home', () => ({
selector({ refreshAgentList: mocks.refreshAgentList }),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@/services/agent', () => ({
agentService: {
updateAgentSlug: (...args: unknown[]) => mocks.updateAgentSlug(...args),
@@ -26,31 +26,6 @@ vi.mock('@/features/ResourcePermission/useResourceAccess', () => ({
useResourceAccess: () => ({ canEditResource: true, isAccessResolved: true }),
}));
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) => (
<div {...props}>{children}</div>
),
}));
vi.mock('@lobehub/ui/icons', () => ({
BotPromptIcon: () => null,
}));
vi.mock('lucide-react', () => ({
DnaIcon: () => null,
ListTodoIcon: () => null,
MessageSquarePlusIcon: () => null,
MessagesSquareIcon: () => null,
SearchIcon: () => null,
TargetIcon: () => null,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('react-router', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const actual = (await vi.importActual('react-router')) as typeof import('react-router');
@@ -12,10 +12,6 @@ const mocks = vi.hoisted(() => ({
params: {} as { aid?: string },
}));
vi.mock('@lobehub/ui', () => ({
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}));
vi.mock('@/features/NavPanel/components/NavItem', () => ({
default: ({
active,
@@ -9,34 +9,11 @@ import MetaHoverCard from './MetaHoverCard';
const fetchTopicLinkedPullRequestMock = vi.hoisted(() => vi.fn());
vi.mock('@lobehub/ui', () => ({
Icon: () => <span data-testid="meta-card-icon" />,
}));
vi.mock('@/store/chat', () => ({
useChatStore: (selector: (state: unknown) => unknown) =>
selector({ useFetchTopicLinkedPullRequest: fetchTopicLinkedPullRequestMock }),
}));
vi.mock('antd-style', () => ({
createStaticStyles: () => ({
card: 'card',
header: 'header',
headerTime: 'headerTime',
headerTitle: 'headerTitle',
prLink: 'prLink',
row: 'row',
rowIcon: 'rowIcon',
rowText: 'rowText',
}),
cssVar: {
colorError: '#f00',
colorSuccess: '#0f0',
colorTextTertiary: '#999',
colorWarning: '#fa0',
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, values?: Record<string, string>) =>
@@ -2,7 +2,7 @@
* @vitest-environment happy-dom
*/
import { render, screen, waitFor } from '@testing-library/react';
import type { CSSProperties, ReactNode } from 'react';
import type { ReactNode } from 'react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import TopicItem from './index';
@@ -17,43 +17,13 @@ const topicMetaCardMock = vi.hoisted(() => ({
value: undefined as { pullRequest?: { state: string } } | undefined,
}));
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) => (
<div {...props}>{children}</div>
),
// Assertions key on the raw lucide displayName, which the real Icon does not
// expose in the DOM.
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
Icon: ({ icon }: { icon?: { displayName?: string } }) => (
<div data-icon={icon?.displayName} data-testid="topic-item-icon" />
),
Popover: ({ children }: { children?: ReactNode }) => <>{children}</>,
Skeleton: {
Button: (props: Record<string, unknown>) => <div {...props} />,
},
Tooltip: ({ children }: { children?: ReactNode }) => <>{children}</>,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
ContextMenuTrigger: ({ children }: { children?: ReactNode }) => <>{children}</>,
Tag: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Text: ({ children, style }: { children?: ReactNode; style?: CSSProperties }) => (
<span style={style}>{children}</span>
),
}));
vi.mock('antd-style', () => ({
// `ContextMenuTrigger` comes from the base-ui barrel, which pulls in
// ScrollArea's global style at import time.
createGlobalStyle: () => () => null,
createStaticStyles: () => ({
dotContainer: 'dotContainer',
neonDot: 'neonDot',
neonDotWrapper: 'neonDotWrapper',
}),
cssVar: {
colorInfo: '#00f',
colorTextDescription: '#999',
},
keyframes: () => 'keyframes',
useTheme: () => ({ isDarkMode: false }),
}));
vi.mock('motion/react', () => ({
@@ -68,12 +38,6 @@ vi.mock('motion/react', () => ({
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@/const/version', () => ({ isDesktop: false }));
vi.mock('@/features/NavPanel/components/NavItem', () => ({
default: ({
@@ -12,17 +12,8 @@ const permissionMock = vi.hoisted(() => ({
}));
const versionMock = vi.hoisted(() => ({ isDesktop: false }));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@lobehub/ui', () => ({
Icon: () => null,
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: {
useApp: () => ({
message: {
@@ -2,7 +2,6 @@
* @vitest-environment happy-dom
*/
import { fireEvent, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import TopicList from './index';
@@ -110,12 +109,6 @@ vi.mock('@/store/user/selectors', () => ({
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('../AllTopicsDrawer', () => ({
default: ({ open }: { open: boolean }) => (
<div data-open={String(open)} data-testid="all-topics-drawer" />
@@ -138,16 +131,6 @@ vi.mock('./Item', () => ({
default: () => <div data-testid="topic-item" />,
}));
// Partial mock: keep every real export (e.g. `lobeStaticStylish`, which
// `createStaticStyles` reads at import time in transitively-loaded modules like
// ShareModal/useContainerStyles) and override only Flexbox. A full mock returning
// just Flexbox drops those exports and crashes collection whenever the suite's
// module graph evaluates one of them.
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
}));
describe('Agent topic list', () => {
beforeEach(() => {
pushMock.mockReset();
@@ -2,7 +2,6 @@
* @vitest-environment happy-dom
*/
import { fireEvent, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import GroupItem from './GroupItem';
@@ -23,67 +22,6 @@ vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await import('~base-ui-stubs')).baseUiStubs,
}));
vi.mock('@lobehub/ui', () => ({
AccordionItem: ({
action,
children,
title,
}: {
action?: ReactNode;
children?: ReactNode;
title?: ReactNode;
}) => (
<section>
<div>
{title}
{action}
</div>
{children}
</section>
),
ActionIcon: ({
onClick,
title,
}: {
onClick?: (event: { stopPropagation: () => void }) => void;
title?: string;
}) => (
<button
aria-label={title}
type="button"
onClick={() => onClick?.({ stopPropagation: vi.fn() })}
/>
),
Center: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Icon: () => <span />,
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
Tooltip: ({ children }: { children?: ReactNode }) => <>{children}</>,
}));
vi.mock('antd-style', async (importOriginal) => ({
...(await importOriginal<object>()),
createStaticStyles: () => ({
addTopicAction: 'addTopicAction',
statusBadge: 'statusBadge',
statusBadgeError: 'statusBadgeError',
statusBadgeLoading: 'statusBadgeLoading',
statusBadgeWaiting: 'statusBadgeWaiting',
unreadDot: 'unreadDot',
unreadRipple: 'unreadRipple',
unreadWrapper: 'unreadWrapper',
}),
cssVar: {
colorError: '#f00',
colorInfo: '#00f',
colorTextSecondary: '#666',
colorTextTertiary: '#999',
colorWarning: '#fa0',
},
cx: (...classes: Array<string | undefined>) => classes.filter(Boolean).join(' '),
keyframes: () => 'keyframes',
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: { directory?: string }) =>
@@ -6,17 +6,8 @@ import { describe, expect, it, vi } from 'vitest';
import { useThreadItemDropdownMenu } from './useDropdownMenu';
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@lobehub/ui', () => ({
Icon: () => null,
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: {
useApp: () => ({
modal: {
@@ -2,7 +2,6 @@
* @vitest-environment happy-dom
*/
import { renderHook } from '@testing-library/react';
import type { ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useTopicActionsDropdownMenu } from './useDropdownMenu';
@@ -43,36 +42,25 @@ vi.mock('@/store/user', () => ({
useUserStore: () => userMock.currentUserId,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
confirmModal: confirmModalMock,
toast: messageMock,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: {
useApp: () => ({
message: messageMock,
modal: {
confirm: vi.fn(),
error: vi.fn(),
},
}),
},
}));
vi.mock('@lobehub/ui', () => ({
Icon: () => null,
}));
vi.mock('antd', () => {
return {
App: {
useApp: () => ({
message: messageMock,
modal: {
confirm: vi.fn(),
error: vi.fn(),
},
}),
},
Upload: ({ children }: { children: ReactNode }) => <>{children}</>,
};
});
vi.mock('@/hooks/usePermission', () => ({
usePermission: (action: 'create_content' | 'edit_own_content') => ({
allowed: permissionMock[action],
@@ -39,11 +39,10 @@ const mocks = vi.hoisted(() => ({
},
}));
vi.mock('@lobehub/ui', () => ({
Center: ({ children }: { children: ReactNode }) => (
<div data-testid="selector-trigger">{children}</div>
),
Flexbox: ({ children }: { children: ReactNode }) => <div>{children}</div>,
// The real Popover only mounts its content after an open interaction; the
// assertions read the selector list synchronously.
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
Popover: ({
children,
content,
@@ -62,19 +61,6 @@ vi.mock('@lobehub/ui', () => ({
),
}));
vi.mock('antd-style', () => ({
createStaticStyles: () => ({
chevron: 'chevron',
container: 'container',
}),
cx: (...classes: string[]) => classes.filter(Boolean).join(' '),
}));
vi.mock('lucide-react', () => ({
ChevronsUpDownIcon: () => <span data-testid="chevron" />,
Circle: () => <span data-testid="circle" />,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => (key === 'taskManager.agent' ? 'Task Manager' : key),
@@ -39,38 +39,15 @@ const mocks = vi.hoisted(() => ({
updateVerifyConfig: vi.fn(),
}));
vi.mock('@lobehub/ui', () => ({
// Real base-ui ActionIcon only surfaces its title via a hover Tooltip; the
// assertions click the title text directly.
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
ActionIcon: ({ onClick, title }: { onClick?: () => void; title?: string }) => (
<button type="button" onClick={onClick}>
{title}
</button>
),
Block: ({ children, onClick }: { children: ReactNode; onClick?: () => void }) => (
<div onClick={onClick}>{children}</div>
),
Drawer: ({ children, open }: { children: ReactNode; open?: boolean }) =>
open ? <aside>{children}</aside> : null,
Flexbox: ({ children, onClick }: { children: ReactNode; onClick?: () => void }) => (
<div onClick={onClick}>{children}</div>
),
Icon: () => <span />,
Tag: ({ children }: { children: ReactNode }) => <span>{children}</span>,
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
ActionIcon: ({ onClick, title }: { onClick?: () => void; title?: string }) => (
<button type="button" onClick={onClick}>
{title}
</button>
),
Button: ({ children, onClick }: { children: ReactNode; onClick?: () => void }) => (
<button type="button" onClick={onClick}>
{children}
</button>
),
Tag: ({ children }: { children: ReactNode }) => <span>{children}</span>,
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
confirmModal: (opts: unknown) => mocks.confirmModal(opts),
}));
@@ -78,33 +55,11 @@ vi.mock('@/features/Workspace/useWorkspaceAwareNavigate', () => ({
useWorkspaceAwareNavigate: () => mocks.navigate,
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: { useApp: () => ({ message: { error: vi.fn() } }) },
}));
vi.mock('antd-style', () => ({
cx: (...classNames: unknown[]) => classNames.filter(Boolean).join(' '),
createStaticStyles: () => ({
body: 'body',
drawerBody: 'drawerBody',
error: 'error',
group: 'group',
groupHeader: 'groupHeader',
list: 'list',
row: 'row',
seq: 'seq',
}),
cssVar: {
colorTextDescription: '#999',
colorTextQuaternary: '#aaa',
colorTextSecondary: '#666',
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@/components/NeuralNetworkLoading', () => ({ default: () => <div>loading</div> }));
vi.mock('@/features/Acceptance', async () => ({
@@ -138,10 +93,6 @@ vi.mock('@/features/Acceptance', async () => ({
},
}));
vi.mock('@/hooks/usePermission', () => ({
usePermission: () => ({ allowed: true }),
}));
vi.mock('@/services/verify', () => ({
verifyService: {
deleteAcceptance: (id: string) => mocks.deleteAcceptance(id),
@@ -35,23 +35,22 @@ const mocks = vi.hoisted(() => ({
updateTaskVisibility: vi.fn(),
}));
vi.mock('@lobehub/ui', () => ({
ActionIcon: ({ title }: { title?: string }) => <button type="button">{title}</button>,
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
DropdownMenu: ({ children, items }: { children?: ReactNode; items: MenuItem[] }) => {
mocks.dropdownItems = items;
return <>{children}</>;
},
Icon: () => <span />,
copyToClipboard: vi.fn(),
}));
vi.mock('@lobehub/ui/base-ui', () => ({
ActionIcon: ({ title }: { title?: string }) => <button type="button">{title}</button>,
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
confirmModal: (opts: unknown) => mocks.confirmModal(opts),
toast: { error: vi.fn(), success: vi.fn() },
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: {
useApp: () => ({
message: { success: mocks.messageSuccess },
@@ -59,12 +58,6 @@ vi.mock('antd', () => ({
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@/business/client/hooks/useActiveWorkspaceId', () => ({
useActiveWorkspaceId: () => mocks.activeWorkspaceId,
}));
@@ -103,10 +96,6 @@ vi.mock('@/hooks/useAppOrigin', () => ({
useAppOrigin: () => 'https://example.com',
}));
vi.mock('@/hooks/usePermission', () => ({
usePermission: () => ({ allowed: true }),
}));
vi.mock('@/store/task', () => ({
useTaskStore: (selector: (state: Record<string, unknown>) => unknown) =>
selector({
@@ -2,7 +2,6 @@
* @vitest-environment happy-dom
*/
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import type { ReactNode } from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import TaskParentBar from './TaskParentBar';
@@ -25,29 +24,6 @@ const createState = (parent: any) => ({
},
});
vi.mock('@lobehub/ui', () => ({
Button: ({
children,
icon,
onClick,
}: {
children: ReactNode;
icon?: ReactNode;
onClick?: () => void;
}) => (
<button type="button" onClick={onClick}>
{icon}
{children}
</button>
),
Flexbox: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('react-router', () => ({
useNavigate: () => mocks.navigate,
}));
@@ -34,38 +34,12 @@ const mocks = vi.hoisted(() => ({
} as any,
}));
vi.mock('@lobehub/ui', () => ({
ActionIcon: ({ onClick }: { onClick?: () => void }) => (
<button type="button" onClick={onClick}>
action
</button>
),
Block: ({
children,
clickable,
onClick,
}: {
children: ReactNode;
clickable?: boolean;
onClick?: () => void;
}) =>
clickable ? (
<button type="button" onClick={onClick}>
{children}
</button>
) : (
<div>{children}</div>
),
Flexbox: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Icon: () => <span>icon</span>,
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}));
vi.mock('@/libs/contextMenu', () => ({
showContextMenu: mocks.showContextMenu,
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: {
useApp: () => ({
message: { error: vi.fn(), info: vi.fn(), success: vi.fn(), warning: vi.fn() },
@@ -101,33 +75,14 @@ vi.mock('antd', () => ({
),
}));
vi.mock('antd-style', () => ({
vi.mock('antd-style', async (importOriginal) => ({
...(await importOriginal<object>()),
cssVar: {
colorTextDescription: '#999',
colorTextSecondary: '#666',
},
}));
vi.mock('@lobehub/ui/base-ui', () => ({
ActionIcon: ({ onClick }: { onClick?: () => void }) => (
<button type="button" onClick={onClick}>
action
</button>
),
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
confirmModal: vi.fn(),
toast: {
error: vi.fn(),
info: vi.fn(),
success: vi.fn(),
warning: vi.fn(),
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('react-router', () => ({
useNavigate: () => mocks.navigate,
}));
@@ -9,28 +9,6 @@ import { describe, expect, it, vi } from 'vitest';
import TopicCard from './TopicCard';
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@lobehub/ui/base-ui', () => ({
ActionIcon: ({ onClick }: { onClick?: () => void }) => (
<button type="button" onClick={onClick}>
action
</button>
),
Avatar: () => <span>avatar</span>,
Tag: ({ children }: { children: ReactNode }) => <span>{children}</span>,
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
confirmModal: vi.fn(),
toast: {
error: vi.fn(),
info: vi.fn(),
success: vi.fn(),
warning: vi.fn(),
},
}));
vi.mock('@/store/task', () => ({
useTaskStore: (selector: (state: any) => unknown) =>
selector({
@@ -42,10 +20,6 @@ vi.mock('@/store/task', () => ({
}),
}));
vi.mock('@/hooks/usePermission', () => ({
usePermission: () => ({ allowed: true }),
}));
vi.mock('@/hooks/useActivityTime', () => ({
useActivityTime: () => ({ text: '4m ago', title: '4m ago' }),
}));
@@ -59,7 +59,8 @@ const mocks = vi.hoisted(() => ({
const serializeSize = (size: unknown) =>
size === undefined ? '' : typeof size === 'string' ? size : JSON.stringify(size);
vi.mock('@lobehub/ui', () => ({
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
copyToClipboard: vi.fn(),
DropdownMenu: ({
children,
@@ -79,19 +80,10 @@ vi.mock('@lobehub/ui', () => ({
)}
</>
),
Flexbox: ({
children,
flex,
style,
}: {
children?: ReactNode;
flex?: CSSProperties['flex'];
style?: CSSProperties;
}) => <div style={{ flex, ...style }}>{children}</div>,
Freeze: ({ children }: { children?: ReactNode; frozen?: boolean }) => <>{children}</>,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
ActionIcon: ({
disabled,
icon,
@@ -116,19 +108,6 @@ vi.mock('@lobehub/ui/base-ui', () => ({
{title}
</button>
),
Tag: ({ children, title }: { children?: ReactNode; title?: string }) => (
<span title={title}>{children}</span>
),
Text: ({ children, style }: { children?: ReactNode; style?: CSSProperties }) => (
<span style={style}>{children}</span>
),
confirmModal: vi.fn(),
toast: {
error: vi.fn(),
info: vi.fn(),
success: vi.fn(),
warning: vi.fn(),
},
FloatingPanel: ({
actions,
children,
@@ -185,12 +164,6 @@ vi.mock('next/dynamic', () => ({
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@/features/Conversation/ChatList', () => ({
default: () => <div data-testid="chat-list" />,
}));
@@ -28,41 +28,8 @@ vi.mock('@lobehub/editor/react', () => ({
}),
}));
// Stub the base-ui Button (submit) to a native button — it needs a
// MotionProvider the app sets up globally but the unit env doesn't.
vi.mock('@lobehub/ui/base-ui', () => ({
Button: ({
children,
disabled,
onClick,
}: {
children?: ReactNode;
disabled?: boolean;
onClick?: () => void;
}) => (
<button disabled={disabled} type="button" onClick={onClick}>
{children}
</button>
),
ActionIcon: ({
onClick,
style,
title,
}: {
onClick?: () => void;
style?: CSSProperties;
title?: string;
}) => (
<div
aria-label={title}
role="button"
style={{ height: 24, width: 24, ...style }}
onClick={onClick}
>
{title}
</div>
),
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
toast: { error: vi.fn(), success: vi.fn() },
}));
@@ -135,10 +102,6 @@ vi.mock('../shared/useAgentVisibility', () => ({
useAgentVisibility: (agentId?: string) => (agentId === 'agent-private' ? 'private' : undefined),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
describe('CreateTaskInlineEntry', () => {
beforeEach(() => {
permissionMock.allowed = true;
@@ -182,7 +145,9 @@ describe('CreateTaskInlineEntry', () => {
expect(assigneeControl?.style.getPropertyValue('--lobe-flex-height')).toBe('24px');
expect(assigneeControl?.style.getPropertyValue('--lobe-flex-padding-block')).toBe('3px');
const attachmentAction = container.querySelector<HTMLElement>('[role="button"]');
const attachmentAction = container
.querySelector('svg.lucide-paperclip')
?.closest<HTMLElement>('button');
expect(attachmentAction).toHaveStyle({ height: '24px', width: '24px' });
expect(attachmentAction?.parentElement?.style.getPropertyValue('--lobe-flex-align')).toBe(
'center',
@@ -12,8 +12,8 @@ const taskStoreMock = vi.hoisted(() => ({
visibility: 'workspace',
}));
vi.mock('@lobehub/ui', () => ({
ActionIcon: () => <span>Visibility</span>,
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
DropdownMenu: ({
children,
items,
@@ -33,11 +33,6 @@ vi.mock('@lobehub/ui', () => ({
Icon: () => <span data-testid="menu-extra-icon" />,
}));
vi.mock('antd-style', async (importOriginal) => ({
...(await importOriginal<object>()),
cssVar: { colorTextSecondary: '#666' },
}));
vi.mock('@/business/client/hooks/useActiveWorkspaceId', () => ({
useActiveWorkspaceId: () => 'workspace-1',
}));
@@ -50,10 +45,6 @@ vi.mock('@/store/task', () => ({
}),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
describe('TaskListVisibilityFilter', () => {
it('shows a trailing checkmark only for the active visibility option', () => {
render(<TaskListVisibilityFilter />);
@@ -2,7 +2,6 @@
* @vitest-environment happy-dom
*/
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import TaskWorkspaceLayout from './TaskWorkspaceLayout';
@@ -11,12 +10,6 @@ const mocks = vi.hoisted(() => ({
isMobile: false,
}));
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) => (
<div {...props}>{children}</div>
),
}));
vi.mock('react-router', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const actual = (await vi.importActual('react-router')) as typeof import('react-router');
@@ -13,28 +13,6 @@ const mocks = vi.hoisted(() => ({
useFetchTaskDetail: vi.fn(),
}));
vi.mock('@lobehub/ui', () => ({
Block: ({
children,
clickable,
onClick,
}: {
children: ReactNode;
clickable?: boolean;
onClick?: () => void;
}) =>
clickable ? (
<button data-testid="task-card" type="button" onClick={onClick}>
{children}
</button>
) : (
<span>{children}</span>
),
ContextMenuTrigger: ({ children }: { children: ReactNode }) => <>{children}</>,
Flexbox: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
i18n: { language: 'en-US' },
@@ -132,7 +110,7 @@ describe('AgentTaskItem', () => {
it('opens an assigned task inside its owning agent route', () => {
render(<AgentTaskItem task={createTask('agt_owner')} />);
fireEvent.click(screen.getByTestId('task-card'));
fireEvent.click(screen.getByText('Hourly trend update'));
expect(mocks.navigate).toHaveBeenCalledWith('/agent/agt_owner/task/T-22');
});
@@ -140,7 +118,7 @@ describe('AgentTaskItem', () => {
it('opens an assigned task on the global detail route in global scope', () => {
render(<AgentTaskItem routeScope="global" task={createTask('agt_owner')} />);
fireEvent.click(screen.getByTestId('task-card'));
fireEvent.click(screen.getByText('Hourly trend update'));
expect(mocks.navigate).toHaveBeenCalledWith('/task/T-22');
});
@@ -148,7 +126,7 @@ describe('AgentTaskItem', () => {
it('falls back to the global task detail route when the task has no assignee', () => {
render(<AgentTaskItem task={createTask(null)} />);
fireEvent.click(screen.getByTestId('task-card'));
fireEvent.click(screen.getByText('Hourly trend update'));
expect(mocks.navigate).toHaveBeenCalledWith('/task/T-22');
});
@@ -7,10 +7,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
import TaskSubtaskProgressTag from './TaskSubtaskProgressTag';
vi.mock('@lobehub/ui', () => ({
Block: ({ children, onClick }: { children: ReactNode; onClick?: () => void }) => (
<div onClick={onClick}>{children}</div>
),
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
DropdownMenu: ({
children,
items,
@@ -32,23 +30,6 @@ vi.mock('@lobehub/ui', () => ({
))}
</div>
),
Flexbox: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}));
vi.mock('antd', () => ({
Progress: () => <span>progress</span>,
}));
vi.mock('antd-style', () => ({
cssVar: { colorSuccess: 'green' },
}));
vi.mock('./TaskStatusIcon', () => ({
@@ -2,25 +2,10 @@
* @vitest-environment happy-dom
*/
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import TaskVisibilityChipLabel from './TaskVisibilityChipLabel';
vi.mock('@lobehub/ui', () => ({
Block: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Icon: () => <span />,
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}));
vi.mock('antd-style', async (importOriginal) => ({
...(await importOriginal<object>()),
cssVar: {
colorTextDescription: '#999',
colorTextSecondary: '#666',
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: { defaultValue?: string }) => {
@@ -7,7 +7,8 @@ import { describe, expect, it, vi } from 'vitest';
import TaskVisibilityTag from './TaskVisibilityTag';
vi.mock('@lobehub/ui', () => ({
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
DropdownMenu: ({
children,
items,
@@ -25,15 +26,6 @@ vi.mock('@lobehub/ui', () => ({
</>
),
Icon: () => <span data-testid="menu-extra-icon" />,
Tooltip: ({ children }: { children: ReactNode }) => children,
}));
vi.mock('antd-style', () => ({
createStaticStyles: () => ({ trigger: 'trigger', triggerDisabled: 'trigger-disabled' }),
cssVar: {
colorTextDescription: '#999',
colorTextSecondary: '#666',
},
}));
vi.mock('@/business/client/hooks/useActiveWorkspaceId', () => ({
@@ -1,6 +1,5 @@
import { act, renderHook } from '@testing-library/react';
import type { ReactNode } from 'react';
import React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { canGoNative } from '@/libs/contextMenu/canGoNative';
@@ -23,18 +22,17 @@ const mocks = vi.hoisted(() => ({
updateTaskStatus: vi.fn(),
}));
vi.mock('@lobehub/ui', () => ({
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
copyToClipboard: mocks.copyToClipboard,
Flexbox: ({ children }: { children?: ReactNode }) => React.createElement('div', {}, children),
Icon: ({ icon: Icon }: { icon?: React.ComponentType }) =>
Icon ? React.createElement(Icon) : React.createElement('span'),
}));
vi.mock('@/libs/contextMenu', () => ({
closeContextMenu: mocks.closeContextMenu,
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: {
useApp: () => ({
message: { success: mocks.messageSuccess },
@@ -51,10 +49,6 @@ vi.mock('@/hooks/useAppOrigin', () => ({
useAppOrigin: () => 'https://example.com',
}));
vi.mock('@/hooks/usePermission', () => ({
usePermission: () => ({ allowed: true }),
}));
vi.mock('@/store/agent', () => ({
useAgentStore: (selector: (state: { inboxAgentId: string }) => unknown) =>
selector({ inboxAgentId: 'inbox-agent' }),
@@ -15,27 +15,6 @@ const createState = (taskDetailMap: Record<string, any>) => ({
taskDetailMap,
});
vi.mock('@lobehub/ui', () => ({
Icon: () => <span>icon</span>,
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}));
vi.mock('antd', () => ({
Breadcrumb: ({ items }: { items: Array<{ key?: string; title: ReactNode }> }) => (
<nav>
{items.map((item, index) => (
<span data-testid="crumb" key={item.key ?? index}>
{item.title}
</span>
))}
</nav>
),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('react-router', () => ({
Link: ({ children, to }: { children: ReactNode; to: string }) => <a href={to}>{children}</a>,
useParams: () => ({}),
+2 -1
View File
@@ -33,7 +33,8 @@ vi.mock('react-router', () => ({
useSearchParams: () => [{ get: mockSearchParamsGet }],
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
toast: { error: mockMessageError, success: mockMessageSuccess },
}));
+2 -1
View File
@@ -15,7 +15,8 @@ vi.mock('react-router', () => ({
useSearchParams: () => [{ get: mockSearchParamsGet }],
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
toast: { error: mockMessageError, success: vi.fn() },
}));
@@ -12,25 +12,11 @@ const tokenMocks = vi.hoisted(() => ({
useTokenBreakdown: vi.fn(),
}));
vi.mock('@lobehub/ui', () => {
const Primitive = ({ children }: { children?: ReactNode }) => createElement('div', {}, children);
return { Center: Primitive, Flexbox: Primitive, Tooltip: Primitive };
});
vi.mock('@lobehub/ui/chat', () => ({
TokenTag: ({ value }: { value: number }) =>
createElement('div', { 'data-testid': 'token-tag' }, value),
}));
vi.mock('antd-style', () => ({
cssVar: new Proxy({}, { get: (_, key) => String(key) }),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@/store/user', () => ({
useUserStore: (selector: (state: object) => unknown) => selector({}),
}));
@@ -72,7 +72,10 @@ vi.mock('@lobechat/const', () => ({
},
}));
vi.mock('@lobehub/ui/base-ui', () => ({ toast: { error: toastError } }));
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
toast: { error: toastError },
}));
vi.mock('@/features/ChatInput/hooks/useAgentId', () => ({
useAgentId: () => 'agent-id',
@@ -84,26 +84,10 @@ vi.mock('@/components/AntdStaticMethods', () => ({
message: { error: vi.fn(), info: vi.fn(), success: vi.fn() },
}));
vi.mock('@lobehub/ui/base-ui', () => ({
toast: { error: vi.fn(), info: vi.fn(), success: vi.fn() },
}));
vi.mock('@/components/RingLoading', () => ({
default: () => <span data-testid="ring-loading" />,
}));
vi.mock('@lobehub/ui', () => ({
Icon: () => <span data-testid="icon" />,
Tooltip: ({ children, title }: { children: ReactNode; title?: ReactNode }) => (
<div data-title={typeof title === 'string' ? title : undefined}>{children}</div>
),
}));
vi.mock('antd-style', () => ({
createStaticStyles: () => ({}),
cssVar: new Proxy({}, { get: () => 'var(--mock)' }),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, values?: Record<string, unknown>) =>
@@ -31,17 +31,16 @@ vi.mock('@/services/git', () => ({
},
}));
vi.mock('@lobehub/ui', () => ({
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
Icon: ({ icon }: any) => <span data-icon={icon?.displayName ?? icon?.name} data-testid="icon" />,
Input: ({ value, onChange, placeholder }: any) => (
<input placeholder={placeholder} value={value} onChange={onChange} />
),
Tooltip: ({ children }: { children: ReactNode }) => (
<span data-testid="worktree-tooltip">{children}</span>
),
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
confirmModal: confirmModalMock,
DropdownMenuItem: ({ children, onClick }: { children: ReactNode; onClick?: () => void }) => (
<button onClick={onClick}>{children}</button>
@@ -63,12 +62,6 @@ vi.mock('@lobehub/ui/base-ui', () => ({
},
}));
vi.mock('antd-style', () => ({
createStaticStyles: () => ({}),
cssVar: new Proxy({}, { get: () => 'var(--mock)' }),
cx: (...classes: string[]) => classes.filter(Boolean).join(' '),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, values?: Record<string, unknown>) =>
@@ -19,8 +19,6 @@ const testState = vi.hoisted(() => ({
},
}));
vi.mock('@lobehub/ui/base-ui', () => ({ confirmModal: vi.fn() }));
vi.mock('@/hooks/useEffectiveAgencyConfig', () => ({
useEffectiveAgencyConfig: () => testState.effective,
}));
@@ -14,7 +14,8 @@ const { manager, preference } = vi.hoisted(() => ({
preference: { terminalFontFamily: '"JetBrains Mono"' },
}));
vi.mock('antd-style', () => ({
vi.mock('antd-style', async (importOriginal) => ({
...(await importOriginal<object>()),
useTheme: () => ({ fontFamilyCode: 'Application Mono' }),
}));
+2 -1
View File
@@ -12,7 +12,8 @@ vi.mock('@lobechat/const', async (importOriginal) => ({
isDesktop: true,
}));
vi.mock('@lobehub/ui', () => ({
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
DraggablePanel: ({ children, expand }: { children?: ReactNode; expand?: boolean }) => (
<div data-expand={String(expand)} data-testid="terminal-panel">
{children}
@@ -2,7 +2,6 @@
* @vitest-environment happy-dom
*/
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ConnectorSourceType } from '@/database/schemas';
@@ -57,33 +56,6 @@ vi.mock('@/hooks/useResourceManageable', () => ({
useResourceManageable: () => true,
}));
vi.mock('@lobehub/ui', () => ({
Tooltip: ({ children }: { children: ReactNode }) => children,
}));
vi.mock('antd', () => ({
App: { useApp: () => ({ message: { error: vi.fn() } }) },
}));
// Stub the base-ui Button to a native button — it needs a MotionProvider the
// app sets up globally but the unit env doesn't.
vi.mock('@lobehub/ui/base-ui', () => ({
Button: ({
children,
disabled,
onClick,
}: {
children?: ReactNode;
disabled?: boolean;
onClick?: () => void;
}) => (
<button disabled={disabled} type="button" onClick={onClick}>
{children}
</button>
),
confirmModal: vi.fn(),
}));
vi.mock('@/store/tool', () => ({
useToolStore<T>(selector: (state: typeof mocks.toolState) => T): T {
return selector(mocks.toolState);
@@ -5,21 +5,9 @@ import { GoalContent } from './GoalModal';
const mocks = vi.hoisted(() => ({ close: vi.fn() }));
vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }) }));
vi.mock('i18next', () => ({ t: (key: string) => key }));
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children }: any) => <div>{children}</div>,
Text: ({ children }: any) => <span>{children}</span>,
TextArea: (props: any) => <textarea {...props} />,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
Button: ({ children, onClick, disabled }: any) => (
<button disabled={disabled} type="button" onClick={onClick}>
{children}
</button>
),
Text: ({ children }: any) => <span>{children}</span>,
createModal: vi.fn(),
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
useModalContext: () => ({ close: mocks.close }),
}));
@@ -14,11 +14,8 @@ let isRegenerating = false;
// Drive the Alert's `afterClose` directly via a click, so we exercise
// ErrorContent's dismiss branching without the real close animation.
vi.mock('@lobehub/ui', () => ({
Skeleton: { Button: () => <div>loading</div> },
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
Alert: ({ action, afterClose }: { action?: ReactNode; afterClose?: () => void }) => (
<div>
<button type="button" onClick={() => afterClose?.()}>
@@ -27,23 +24,6 @@ vi.mock('@lobehub/ui/base-ui', () => ({
{action}
</div>
),
Button: ({
children,
disabled,
loading,
}: {
children?: ReactNode;
disabled?: boolean;
loading?: boolean;
}) => (
<button aria-busy={loading || undefined} disabled={disabled} type="button">
{children}
</button>
),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@/features/Conversation/store', () => ({
@@ -15,8 +15,6 @@ import {
useConversationScroll,
} from './useConversationScroll';
vi.mock('zustand/traditional');
vi.mock('../../store', async (importOriginal) => {
const actual = await importOriginal<typeof ConversationStoreModule>();
return {
@@ -23,27 +23,6 @@ const chatListMocks = vi.hoisted(() => ({
useFetchAgentConfig: vi.fn(),
}));
vi.mock('@lobehub/ui/base-ui', () => ({
Button: ({
children,
disabled,
onClick,
}: {
children?: ReactNode;
disabled?: boolean;
onClick?: () => void;
}) => (
<button disabled={disabled} onClick={onClick}>
{children}
</button>
),
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@/features/Conversation/ChatList/components/AgentSignalReceiptList', () => ({
default: () => null,
}));
@@ -1,10 +1,8 @@
import type * as businessConstModule from '@lobechat/business-const';
import { HeterogeneousAgentSessionErrorCode } from '@lobechat/electron-client-ipc';
import type * as modelRuntimeModule from '@lobechat/model-runtime';
import { AgentRuntimeErrorType } from '@lobechat/model-runtime';
import type * as lobechatTypesModule from '@lobechat/types';
import { ChatErrorType } from '@lobechat/types';
import type * as lobehubUiModule from '@lobehub/ui';
import { fireEvent, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
@@ -32,14 +30,6 @@ const businessErrorContentMock = vi.hoisted(() =>
})),
);
vi.mock('@lobechat/business-const', async (importOriginal) => {
const actual = (await importOriginal()) as typeof businessConstModule;
return {
...actual,
};
});
vi.mock('@lobechat/model-runtime', async (importOriginal) => {
const actual = (await importOriginal()) as typeof modelRuntimeModule;
@@ -64,20 +54,6 @@ vi.mock('@lobechat/types', async (importOriginal) => {
};
});
vi.mock('@lobehub/ui', async (importOriginal) => {
const actual = (await importOriginal()) as typeof lobehubUiModule;
return {
...actual,
Block: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Highlighter: ({ children }: { children?: ReactNode }) => <pre>{children}</pre>,
Skeleton: {
...actual.Skeleton,
Button: () => <div>loading</div>,
},
};
});
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: Record<string, unknown>) =>
@@ -35,19 +35,6 @@ const mockOpenDocument = vi.fn();
const mockOpenTaskDetail = vi.fn();
const mockOpenVerifyReport = vi.fn();
vi.mock('@lobehub/ui/base-ui', () => ({
ActionIcon: (props: { onClick?: (e: unknown) => void; title?: string }) => (
<button
aria-label={props.title}
data-side-browser={(props as Record<string, unknown>)['data-side-browser']}
type="button"
onClick={props.onClick}
/>
),
Avatar: ({ alt }: { alt?: string }) => <span>{alt}</span>,
Text: ({ children }: { children?: unknown }) => <span>{children as never}</span>,
}));
vi.mock('@/business/client/hooks/useWorkspaces', () => ({
useWorkspaces: () => [{ id: 'ws-1', slug: 'lobe-team' }],
}));
@@ -2,8 +2,9 @@
* @vitest-environment happy-dom
*/
import { RENDERER_HANDLED_LINK_ATTR } from '@lobechat/desktop-bridge';
import type { TooltipProps } from '@lobehub/ui';
import { fireEvent, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import type { ComponentType } from 'react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { useChatStore } from '@/store/chat';
@@ -40,29 +41,19 @@ vi.mock('@/components/FileIcon', () => ({
),
}));
vi.mock('@lobehub/ui', async (importOriginal) => ({
...((await importOriginal()) as Record<string, unknown>),
Tooltip: ({
children,
mouseEnterDelay,
placement,
title,
}: {
children: ReactNode;
mouseEnterDelay?: number;
placement?: string;
title?: ReactNode;
}) => (
<span
data-mouse-enter-delay={String(mouseEnterDelay)}
data-placement={placement}
data-testid="local-file-tooltip"
data-title={typeof title === 'string' ? title : undefined}
>
{children}
</span>
),
}));
const tooltipPropsSpy = vi.hoisted(() => vi.fn());
vi.mock('@lobehub/ui', async (importOriginal) => {
const actual = await importOriginal<Record<string, unknown>>();
const ActualTooltip = actual.Tooltip as ComponentType<TooltipProps>;
return {
...actual,
Tooltip: (props: TooltipProps) => {
tooltipPropsSpy(props);
return <ActualTooltip {...props} />;
},
};
});
describe('LocalFileLink Render', () => {
afterEach(() => {
@@ -97,15 +88,13 @@ describe('LocalFileLink Render', () => {
const link = screen.getByRole('link', { name: 'Group.tsx' });
expect(screen.getByTestId('local-file-tooltip')).toHaveAttribute(
'data-title',
'/Users/me/project/src/Group.tsx (line 265)',
expect(tooltipPropsSpy).toHaveBeenCalledWith(
expect.objectContaining({
mouseEnterDelay: 0.1,
placement: 'topLeft',
title: '/Users/me/project/src/Group.tsx (line 265)',
}),
);
expect(screen.getByTestId('local-file-tooltip')).toHaveAttribute(
'data-mouse-enter-delay',
'0.1',
);
expect(screen.getByTestId('local-file-tooltip')).toHaveAttribute('data-placement', 'topLeft');
fireEvent.click(link);
@@ -2,48 +2,28 @@
* @vitest-environment happy-dom
*/
import { fireEvent, render, screen } from '@testing-library/react';
import type { ComponentType, ReactNode } from 'react';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import Arguments from './index';
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Highlighter: ({ children, wrap }: { children?: ReactNode; wrap?: boolean }) => (
<pre data-testid="highlighter" data-wrap={String(Boolean(wrap))}>
{children}
</pre>
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
ActionIcon: ({
active,
onClick,
title,
}: {
active?: boolean;
onClick?: () => void;
title?: string;
}) => (
<button aria-pressed={active} type="button" onClick={onClick}>
{title}
</button>
),
}));
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => {
const actual = (await importOriginal()) as Record<string, unknown>;
return {
...actual,
ActionIcon: ({
active,
icon: IconComponent,
onClick,
title,
}: {
active?: boolean;
icon?: ComponentType;
onClick?: () => void;
title?: string;
}) => (
<button aria-pressed={active} type="button" onClick={onClick}>
{title}
{IconComponent ? <IconComponent /> : null}
</button>
),
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
};
});
vi.mock('antd', () => ({
Divider: () => <hr />,
}));
vi.mock('@/components/Descriptions', () => ({
default: ({
items,
@@ -2,7 +2,6 @@
* @vitest-environment happy-dom
*/
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import FallbackIntervention from './Fallback';
@@ -13,19 +12,6 @@ const metaMap: Record<string, { avatar?: string; title?: string }> = {
'search': { title: 'Web Search' },
};
vi.mock('@lobehub/ui', () => ({
ActionIcon: ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) => (
<button {...props}>{children}</button>
),
Avatar: ({ avatar, title }: { avatar?: string; title?: string }) => (
<img alt={title} src={avatar} />
),
Flexbox: ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) => (
<div {...props}>{children}</div>
),
Icon: () => <span>icon</span>,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: { count?: number; defaultValue?: string }) =>
@@ -1,23 +1,18 @@
/**
* @vitest-environment happy-dom
*/
import { WebOnboardingApiName } from '@lobechat/builtin-tool-web-onboarding';
import { WebOnboardingInterventions } from '@lobechat/builtin-tool-web-onboarding/client';
import { fireEvent, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
vi.mock('@lobehub/ui', () => ({
Avatar: ({ avatar }: { avatar: string }) => <div>{avatar}</div>,
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
EmojiPicker: ({ onChange, value }: { onChange?: (next: string) => void; value?: string }) => (
<button data-testid="emoji-picker" type="button" onClick={() => onChange?.('🪶')}>
{value || ''}
</button>
),
Flexbox: ({ children }: { children?: ReactNode; [key: string]: unknown }) => (
<div>{children}</div>
),
Text: ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) => (
<span {...props}>{children}</span>
),
}));
vi.mock('react-i18next', () => ({
@@ -38,13 +33,9 @@ vi.mock('react-i18next', () => ({
}));
describe('web onboarding intervention registry', () => {
let Component: ReturnType<typeof Object> | undefined;
const Component = WebOnboardingInterventions[WebOnboardingApiName.saveUserQuestion];
beforeEach(async () => {
const { WebOnboardingInterventions } =
await import('@lobechat/builtin-tool-web-onboarding/client');
const { WebOnboardingApiName } = await import('@lobechat/builtin-tool-web-onboarding');
Component = WebOnboardingInterventions[WebOnboardingApiName.saveUserQuestion];
it('registers the saveUserQuestion intervention', () => {
expect(Component).toBeDefined();
});
@@ -90,8 +81,8 @@ describe('web onboarding intervention registry', () => {
if (!Component) throw new TypeError('Expected web onboarding intervention to be registered');
const onArgsChange = vi.fn();
let beforeApproveCallback: (() => Promise<void>) | undefined;
const registerBeforeApprove = (_id: string, callback: () => Promise<void>) => {
let beforeApproveCallback: (() => void | Promise<void>) | undefined;
const registerBeforeApprove = (_id: string, callback: () => void | Promise<void>) => {
beforeApproveCallback = callback;
return () => {
beforeApproveCallback = undefined;
@@ -2,15 +2,10 @@
* @vitest-environment happy-dom
*/
import { act, cleanup, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import ExecutionTime from './ExecutionTime';
vi.mock('@lobehub/ui', () => ({
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
}));
describe('ExecutionTime', () => {
afterEach(() => {
cleanup();
@@ -15,15 +15,6 @@ const retryFailedAssistantStepMock = vi.fn();
const navigateMock = vi.fn();
let isInReasoningMock = false;
vi.mock('@lobehub/ui', () => ({
Block: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Highlighter: ({ children }: { children?: ReactNode }) => <pre>{children}</pre>,
Skeleton: {
Button: () => <div>loading</div>,
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
i18n: {
@@ -69,10 +60,6 @@ vi.mock('@/features/Electron/HeterogeneousAgent/StatusGuide', () => ({
),
}));
vi.mock('@/hooks/usePermission', () => ({
usePermission: () => ({ allowed: true }),
}));
vi.mock('@/hooks/useProviderName', () => ({
useProviderName: () => 'Mock Provider',
}));
@@ -2,26 +2,11 @@
* @vitest-environment happy-dom
*/
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import ContentBlocksScroll from './ContentBlocksScroll';
import type { RenderableAssistantContentBlock } from './types';
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children, gap }: { children?: ReactNode; gap?: number }) => (
<div data-gap={gap}>{children}</div>
),
ScrollArea: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
}));
vi.mock('antd-style', () => ({
createStaticStyles: () => ({
scrollTask: 'scroll-task',
scrollWorkflow: 'scroll-workflow',
}),
}));
vi.mock('./ContentBlock', () => ({
default: ({ disableMarkdownStreaming, id }: RenderableAssistantContentBlock) => (
<div
@@ -66,7 +51,7 @@ describe('ContentBlocksScroll', () => {
});
it('uses a consistent gap between workflow blocks', () => {
const { container } = render(
render(
<ContentBlocksScroll
assistantId="assistant-1"
scroll={false}
@@ -78,6 +63,7 @@ describe('ContentBlocksScroll', () => {
/>,
);
expect(container.querySelector('[data-gap="8"]')).toBeInTheDocument();
const [firstBlock] = screen.getAllByTestId('content-block');
expect(firstBlock.parentElement!.style.getPropertyValue('--lobe-flex-gap')).toBe('8px');
});
});
@@ -15,16 +15,6 @@ let mockIsGenerating = false;
let mockDbMessages: { createdAt?: Date | number | string | null; id: string }[] = [];
let mockOperations: { metadata: Record<string, unknown>; status: string }[] = [];
vi.mock('@lobehub/ui', () => ({
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
}));
vi.mock('antd-style', () => ({
createStaticStyles: () => ({
container: 'group-container',
}),
}));
vi.mock('@/store/chat', () => ({
useChatStore: (selector: (state: unknown) => unknown) => selector({}),
}));
@@ -11,13 +11,6 @@ let mockStoreContent = 'original full content';
let mockStoreHasTools = true;
let mockStoreMessage: { createdAt?: number } | undefined = { createdAt: 1000 };
vi.mock('antd-style', () => ({
createStaticStyles: () => ({
pWithTool: 'tool-line',
}),
cx: (...values: unknown[]) => values.filter(Boolean).join(' '),
}));
vi.mock('@/features/Conversation/Markdown', () => ({
default: ({ children, className }: { children?: ReactNode; className?: string }) => (
<div className={className} data-testid="markdown">
@@ -11,7 +11,8 @@ import WorkflowCollapse from './WorkflowCollapse';
let mockIsGenerating = true;
vi.mock('@lobehub/ui', () => ({
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
Accordion: ({
children,
expandedKeys,
@@ -64,8 +65,6 @@ vi.mock('@lobehub/ui', () => ({
{IconComponent ? <IconComponent /> : null}
</button>
),
Block: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Icon: ({ icon: IconComponent }: { icon?: ComponentType }) =>
IconComponent ? (
<div
@@ -77,25 +76,11 @@ vi.mock('@lobehub/ui', () => ({
) : (
<div />
),
ShikiLobeTheme: {},
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
ActionIcon: ({
icon: IconComponent,
onClick,
title,
}: {
icon?: ComponentType;
onClick?: (e: unknown) => void;
title?: string;
}) => (
<button aria-label={title} type="button" onClick={onClick}>
{IconComponent ? <IconComponent /> : null}
</button>
),
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
...(await import('~base-ui-stubs')).baseUiStubs,
}));
vi.mock('motion/react', () => ({
@@ -26,10 +26,6 @@ vi.mock('@/store/chat', () => ({
selector({ activeTopicId: mocks.topicId, openTopicComments: mocks.openTopicComments }),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
const build = () =>
renderHook(() =>
commentsAction.useBuild({
@@ -27,11 +27,13 @@ vi.mock('@lobehub/ui', () => ({
copyToClipboard: mocks.copyToClipboard,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
toast: { success: mocks.messageSuccess },
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: { useApp: () => ({ message: { success: mocks.messageSuccess } }) },
}));
@@ -68,10 +70,6 @@ vi.mock('@/store/user/selectors', () => ({
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
const build = (
data: Partial<UIChatMessage> = {},
role: MessageActionContext['role'] = 'assistant',
@@ -16,10 +16,6 @@ vi.mock('../../../../store', () => ({
selector({ deleteAssistantMessage, deleteMessage }),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
// A group's id IS its head child's id — the assistantGroup bubble is a virtual
// message built from the run's first assistant row. Fixtures mirror that.
const build = (
@@ -26,10 +26,6 @@ vi.mock('../../../../store', () => ({
}),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
const build = (
data: Partial<UIChatMessage>,
role: MessageActionContext['role'] = 'assistant',
@@ -29,18 +29,16 @@ vi.mock('@/store/file', () => ({
const { messageSuccess } = vi.hoisted(() => ({ messageSuccess: vi.fn() }));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
toast: { success: messageSuccess },
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: { useApp: () => ({ message: { success: messageSuccess } }) },
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
const build = (data: Partial<UIChatMessage>, role: MessageActionContext['role'] = 'user') =>
renderHook(() => restoreToInputAction.useBuild({ data: data as UIChatMessage, id: 'm1', role }))
.result.current;
@@ -14,7 +14,8 @@ const actionMocks = vi.hoisted(() => ({
commentsAvailable: true,
}));
vi.mock('@lobehub/ui', () => ({
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
ActionIconGroup: ({
items,
menu,
@@ -1,5 +1,5 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import type { ReactNode, Ref } from 'react';
import type { Ref } from 'react';
import { useImperativeHandle } from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@@ -185,32 +185,11 @@ vi.mock('@/store/chat', () => ({
const messageSpy = vi.hoisted(() => ({ warning: vi.fn() }));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
message: messageSpy,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...((await importOriginal()) as Record<string, unknown>),
ActionIcon: ({ onClick }: { onClick?: () => void }) => (
<button type={'button'} onClick={onClick} />
),
}));
vi.mock('@lobehub/ui', () => ({
Center: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
copyToClipboard: vi.fn(),
Empty: ({ description }: { description?: ReactNode }) => <div>{description}</div>,
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Icon: () => <span />,
stopPropagation: vi.fn(),
}));
vi.mock('antd-style', async (importOriginal) => {
const actual = (await importOriginal()) as Record<string, unknown>;
@@ -4,7 +4,6 @@ import {
AGENT_DOCUMENT_SKILL_CATEGORY,
AGENT_SIGNAL_SOURCE_TYPE,
} from '@lobechat/const';
import type { ErrorBoundary as LobeErrorBoundary } from '@lobehub/ui';
import { fireEvent, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import type * as ReactRouterDom from 'react-router';
@@ -23,66 +22,13 @@ const removeDocumentMock = vi.hoisted(() => vi.fn());
const useParamsMock = vi.hoisted(() => vi.fn());
const documentExplorerShouldThrow = vi.hoisted(() => ({ current: false }));
vi.mock('@lobehub/ui/base-ui', () => ({
ActionIcon: ({ onClick, title }: { onClick?: (e: React.MouseEvent) => void; title?: string }) => (
<button aria-label={title} onClick={onClick}>
{title}
</button>
),
Alert: ({ message, title }: { message?: ReactNode; title?: ReactNode }) => (
<div role="alert">
<div>{title}</div>
<div>{message}</div>
</div>
),
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
...(await import('~base-ui-stubs')).baseUiStubs,
confirmModal: modalConfirm,
createModal: vi.fn(),
toast: { error: messageError, success: messageSuccess },
}));
vi.mock('@lobehub/ui', async (importOriginal) => {
const actual = await importOriginal<{ ErrorBoundary: typeof LobeErrorBoundary }>();
return {
Accordion: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
AccordionItem: ({ children, title }: { children?: ReactNode; title?: ReactNode }) => (
<div>
{title}
{children}
</div>
),
ActionIcon: ({
onClick,
title,
}: {
onClick?: (e: React.MouseEvent) => void;
title?: string;
}) => (
<button aria-label={title} onClick={onClick}>
{title}
</button>
),
Center: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Empty: ({ description }: { description?: ReactNode }) => <div>{description}</div>,
ErrorBoundary: actual.ErrorBoundary,
Flexbox: ({
children,
onClick,
...props
}: {
children?: ReactNode;
onClick?: () => void;
[key: string]: unknown;
}) => (
<div onClick={onClick} {...props}>
{children}
</div>
),
Highlighter: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Text: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
};
});
vi.mock('@/components/NeuralNetworkLoading', () => ({
default: () => <div data-testid="neural-network-loading" />,
}));
@@ -5,27 +5,11 @@ import { FileItemHeader } from '../FileItem';
const mockRevealInFilesTab = vi.fn();
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => {
const actual = (await importOriginal()) as Record<string, unknown>;
return {
...actual,
ActionIcon: ({ icon: _icon, title, onClick, ...rest }: any) => (
<button aria-label={title} type="button" onClick={onClick} {...rest} />
),
};
});
vi.mock('@/store/global', () => ({
useGlobalStore: (selector: (s: any) => any) =>
selector({ revealInFilesTab: mockRevealInFilesTab }),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@/components/AntdStaticMethods', () => ({
message: { error: vi.fn(), success: vi.fn() },
}));
@@ -266,16 +266,11 @@ vi.mock('@/store/user/selectors', () => ({
labPreferSelectors: { enableInAppBrowser: () => true },
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@lobehub/ui', () => ({
vi.mock('@lobehub/ui', async (importOriginal) => ({
...(await importOriginal<object>()),
ActionIcon: ({ onClick, title }: { onClick?: () => void; title?: string }) => (
<button aria-label={title} type="button" onClick={onClick} />
),
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Icon: () => <span />,
Skeleton: () => <div data-testid="params-loading" />,
}));
@@ -7,8 +7,6 @@ import type * as ConversationStoreModule from '../store';
import { useConversationStore } from '../store';
import { useAgentMeta, useIsBuiltinAgent } from './useAgentMeta';
vi.mock('zustand/traditional');
// Mock the ConversationStore
vi.mock('../store', async (importOriginal) => {
const actual = await importOriginal<typeof ConversationStoreModule>();
@@ -27,10 +27,6 @@ vi.mock('@/features/ResourcePermission/useResourceAccess', () => ({
useResourceAccess: (...args: unknown[]) => mocks.useResourceAccess(...(args as [])),
}));
vi.mock('@/hooks/usePermission', () => ({
usePermission: () => ({ allowed: true }),
}));
vi.mock('@/store/agent', () => ({
useAgentStore: (selector: (state: typeof mocks.agentState) => unknown) =>
selector(mocks.agentState),
@@ -9,7 +9,8 @@ import { confirmRemoveTopic } from './index';
const confirmModalMock = vi.hoisted(() => vi.fn());
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
confirmModal: confirmModalMock,
}));
@@ -1,8 +1,9 @@
import type { DataSyncConfig } from '@lobechat/electron-client-ipc';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import LoginStep from './LoginStep';
const mockElectronState = vi.hoisted(() => ({
clearRemoteServerSyncError: vi.fn(),
connectRemoteServer: vi.fn(),
@@ -19,56 +20,6 @@ vi.mock('@lobechat/electron-client-ipc', () => ({
useWatchBroadcast: vi.fn(),
}));
vi.mock('@lobehub/ui', () => {
const Button = ({
children,
disabled,
onClick,
}: {
children: ReactNode;
disabled?: boolean;
onClick?: () => void;
}) => (
<button disabled={disabled} type="button" onClick={onClick}>
{children}
</button>
);
return {
Alert: ({ description, title }: { description?: ReactNode; title?: ReactNode }) => (
<section>
<h2>{title}</h2>
<p>{description}</p>
</section>
),
Button,
Center: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Flexbox: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Icon: () => <span />,
Input: (props: React.InputHTMLAttributes<HTMLInputElement>) => <input {...props} />,
Text: ({ as, children }: { as?: 'p' | 'span'; children: ReactNode }) =>
as === 'p' ? <p>{children}</p> : <span>{children}</span>,
};
});
vi.mock('antd', () => ({
Divider: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}));
vi.mock('antd-style', async (importOriginal) => {
const actual = (await importOriginal()) as Record<string, unknown>;
return {
...actual,
createStaticStyles: () => ({}),
cssVar: {
colorFillSecondary: '#eee',
colorTextDescription: '#888',
colorTextSecondary: '#666',
},
};
});
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: string | Record<string, string>) => {
@@ -159,7 +110,6 @@ vi.mock('../components/LobeMessage', () => ({
}));
const renderLoginStep = async (props: { mode?: 'onboarding' | 'status' } = {}) => {
const { default: LoginStep } = await import('./LoginStep');
const onBack = vi.fn();
const onNext = vi.fn();
@@ -32,12 +32,6 @@ const mockDocumentStore = {
useFetchDocument,
};
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('zustand-utils', () => ({
createStoreUpdater: () => () => undefined,
}));
@@ -39,14 +39,6 @@ vi.mock('@lobehub/editor', () => ({
ReactToolbarPlugin: vi.fn(),
}));
vi.mock('@lobehub/ui/base-ui', () => ({
ActionIcon: ({ onClick, title }: { onClick?: (e: any) => void; title?: string }) => (
<button aria-label={title} onClick={onClick}>
{title}
</button>
),
}));
vi.mock('@/features/ChatInput/InputEditor/plugins', () => ({
createChatInputRichPlugins: () => [],
}));
@@ -11,17 +11,9 @@ const messageLoadingMock = vi.hoisted(() => vi.fn());
const messageDestroyMock = vi.hoisted(() => vi.fn());
const messageErrorMock = vi.hoisted(() => vi.fn());
vi.mock('@lobehub/ui/base-ui', () => ({
toast: {
error: messageErrorMock,
loading: messageLoadingMock,
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
toast: { error: messageErrorMock, loading: messageLoadingMock },
}));
vi.mock('react-router', async () => {
+3 -5
View File
@@ -3,11 +3,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { openEditorModal } from '.';
vi.mock('@lobehub/ui/base-ui', () => ({
Button: () => null,
createModal: vi.fn(() => ({ close: vi.fn(), destroy: vi.fn(), update: vi.fn() })),
ModalFooter: () => null,
useModalContext: () => ({ close: vi.fn() }),
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
...(await import('~base-ui-stubs')).baseUiStubs,
}));
vi.mock('./EditorModalContent', () => ({ default: () => null }));
@@ -42,46 +42,13 @@ vi.mock('@lobechat/electron-client-ipc', () => ({
},
}));
vi.mock('@lobehub/ui', () => ({
Button: ({
children,
disabled,
onClick,
}: {
children?: ReactNode;
disabled?: boolean;
onClick?: () => void;
}) => (
<button disabled={disabled} type="button" onClick={onClick}>
{children}
</button>
),
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
Icon: () => <span data-testid="modal-icon" />,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
Button: ({
children,
disabled,
loading,
onClick,
}: {
children?: ReactNode;
disabled?: boolean;
loading?: boolean;
onClick?: () => void;
}) => (
<button disabled={disabled || loading} type="button" onClick={onClick}>
{children}
</button>
),
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
createModal: (props: ModalProps) => {
createModalMock(props);
return modalInstance;
},
ModalFooter: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
}));
vi.mock('react-i18next', () => ({
@@ -13,10 +13,6 @@ vi.mock('@lobechat/electron-client-ipc', () => ({
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('motion/react', async () => {
const React = await import('react');
return {
@@ -1,5 +1,4 @@
import { act, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import NavigationBar from './NavigationBar';
@@ -14,32 +13,11 @@ vi.mock('@lobechat/electron-client-ipc', () => ({
useWatchBroadcast: (event: string, handler: () => void) => mocks.handlers.set(event, handler),
}));
vi.mock('@lobehub/ui', () => ({
ActionIcon: ({ icon: _icon, ...props }: Record<string, unknown>) => <button {...props} />,
Flexbox: ({ children, ...props }: { children: ReactNode }) => <div {...props}>{children}</div>,
Popover: ({
children,
content,
open,
}: {
children: ReactNode;
content: ReactNode;
open: boolean;
}) => (
<div>
{children}
{open && content}
</div>
),
Tooltip: ({ children }: { children: ReactNode }) => children,
}));
vi.mock('antd-style', async (importOriginal) => ({
...(await importOriginal<object>()),
createStaticStyles: () => ({ clock: 'clock' }),
}));
vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }) }));
vi.mock('@/features/NavPanel/ToggleLeftPanelButton', () => ({ default: () => null }));
vi.mock('@/features/Workspace/useWorkspaceAwareNavigate', () => ({
useWorkspaceAwareNavigate: () => mocks.navigate,
@@ -1,6 +1,5 @@
import { AGENT_DOCUMENT_CATEGORY, CUSTOM_FOLDER_FILE_TYPE } from '@lobechat/const';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import type { ReactNode } from 'react';
import { useRef } from 'react';
import { MemoryRouter } from 'react-router';
import { describe, expect, it, vi } from 'vitest';
@@ -15,19 +14,6 @@ import ExplorerTree, { getItemPathFromEventPath } from './ExplorerTree';
const showContextMenu = vi.hoisted(() => vi.fn());
vi.mock('@lobehub/ui', () => ({
ActionIcon: ({ onClick, title }: { onClick?: () => void; title?: string }) => (
<button aria-label={title} onClick={onClick}>
{title}
</button>
),
Flexbox: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
genCdnUrl: () => '',
Icon: () => <span />,
showContextMenu,
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
}));
vi.mock('@/libs/contextMenu', () => ({
showContextMenu,
}));
@@ -36,18 +22,8 @@ vi.mock('@lobehub/ui/icons', () => ({
SkillsIcon: () => null,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
ActionIcon: ({ onClick, title }: { onClick?: () => void; title?: string }) => (
<button aria-label={title} onClick={onClick}>
{title}
</button>
),
confirmModal: vi.fn(),
DropdownMenu: ({ children }: { children: ReactNode }) => children,
Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
}));
vi.mock('antd', () => ({
vi.mock('antd', async (importOriginal) => ({
...(await importOriginal<object>()),
App: {
useApp: () => ({
message: { error: vi.fn(), success: vi.fn(), warning: vi.fn() },
@@ -66,12 +42,6 @@ vi.mock('@/features/Workspace/useWorkspaceAwareNavigate', () => ({
useWorkspaceAwareNavigate: () => vi.fn(),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
const dispatchRealContextMenuEventRetargetedPastShadowRoot = (
shadowRow: HTMLElement,
shadowHost: Element,
@@ -2,39 +2,11 @@
* @vitest-environment happy-dom
*/
import { render, screen } from '@testing-library/react';
import type { CSSProperties, ReactNode } from 'react';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import ChatBody from './ChatBody';
vi.mock('@lobehub/ui', () => ({
Flexbox: ({
children,
flex,
height,
style,
width,
...props
}: {
children?: ReactNode;
flex?: number;
height?: string;
style?: CSSProperties;
width?: string;
[key: string]: unknown;
}) => (
<div
data-flex={flex === undefined ? '' : String(flex)}
data-height={height ?? ''}
data-width={width ?? ''}
style={style}
{...props}
>
{children}
</div>
),
}));
vi.mock('@/features/Conversation', () => ({
ChatList: ({ welcome }: { welcome?: ReactNode }) => (
<div data-testid="floating-chat-list">
@@ -54,8 +26,8 @@ describe('FloatingChatPanel ChatBody', () => {
const body = screen.getByTestId('floating-chat-panel-body');
const list = screen.getByTestId('floating-chat-list');
expect(body).toHaveAttribute('data-flex', '1');
expect(body).toHaveAttribute('data-height', '100%');
expect(body.style.getPropertyValue('--lobe-flex')).toBe('1');
expect(body.style.getPropertyValue('--lobe-flex-height')).toBe('100%');
expect(body).toContainElement(list);
expect(list).toContainElement(screen.getByTestId('agent-welcome'));
expect(body).toHaveStyle({ overflow: 'hidden' });
@@ -45,10 +45,6 @@ vi.mock('@/features/Conversation/store', () => ({
selector(mockConversationState.current),
}));
vi.mock('@lobehub/ui', () => ({
Icon: ({ icon }: { icon: () => void }) => <span data-testid="icon">{icon.name}</span>,
}));
describe('FloatingChatPanel InputRow', () => {
it('does not render the expand affordance when disabled', () => {
render(<InputRow isCollapsed={false} showExpandBar={false} onExpand={() => {}} />);
+2 -23
View File
@@ -16,7 +16,8 @@ const sheetHandlers = vi.hoisted(() => ({
},
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
ActionIcon: ({
onClick,
title,
@@ -77,28 +78,6 @@ vi.mock('@lobehub/ui/base-ui', () => ({
},
}));
vi.mock('@lobehub/ui', () => ({
ActionIcon: ({
onClick,
title,
...rest
}: {
onClick?: () => void;
title?: string;
[key: string]: unknown;
}) => (
<button
data-testid={(rest as any)['data-testid']}
title={title}
type="button"
onClick={onClick}
>
{title}
</button>
),
Icon: ({ icon }: { icon: () => void }) => <span data-icon={icon.name} />,
}));
const mergedHooksCaptured = vi.hoisted(() => ({
current: undefined as
| undefined
@@ -26,10 +26,6 @@ const mocks = vi.hoisted(() => ({
sidebarVisibilityOverrides: {} as Record<string, boolean>,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@/business/client/hooks/useActiveWorkspaceId', () => ({
useActiveWorkspaceId: () => mocks.activeWorkspaceId,
}));
+2 -5
View File
@@ -91,11 +91,8 @@ const activeWorkspaceIdMock = vi.hoisted(() => ({
value: null as string | null,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
toast: { error: messageErrorMock },
}));
@@ -41,11 +41,8 @@ vi.mock('antd', async (importOriginal) => {
};
});
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@lobehub/ui/base-ui', () => ({
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
confirmModal: mocks.confirmModal,
toast: { error: mocks.toastError, success: vi.fn() },
}));
@@ -12,23 +12,10 @@ vi.mock('@lobehub/ui', () => ({
Icon: () => null,
}));
vi.mock('@lobehub/ui/base-ui', () => ({
confirmModal: vi.fn(),
toast: { error: vi.fn(), success: vi.fn() },
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock('@/business/client/hooks/useActiveWorkspaceId', () => ({
useActiveWorkspaceId: () => null,
}));
vi.mock('@/hooks/usePermission', () => ({
usePermission: () => ({ allowed: true }),
}));
vi.mock('@/libs/trpc/client', () => ({
lambdaClient: {},
}));
@@ -12,12 +12,6 @@ vi.mock('@lobehub/ui', () => ({
Icon: () => null,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@/features/HomeSidebar/Body/CustomizeSidebarModal', () => ({
openCustomizeSidebarModal: vi.fn(),
}));

Some files were not shown because too many files have changed in this diff Show More