chore: import upstream snapshot with attribution
CI / Run CI (push) Has been cancelled
CI / check-backend (push) Has been cancelled
CI / check-frontend (push) Has been cancelled
CI / tests (push) Has been cancelled
CI / e2e-tests (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:48:47 +08:00
commit b7f52be4c9
653 changed files with 105877 additions and 0 deletions
+306
View File
@@ -0,0 +1,306 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
IStep,
favoriteMessagesState,
useConfig
} from '@chainlit/react-client';
import { FavoriteButton } from '@/components/chat/MessageComposer/FavoriteButton';
const toggleMessageFavoriteMock = vi.fn();
vi.mock('@/components/i18n/Translator', () => ({
useTranslation: () => ({
t: (key: string) => {
const trans: Record<string, string> = {
'chat.favorites.use': 'Use favorite',
'chat.favorites.headline': 'Favorites List',
'chat.favorites.empty.title': 'No Saved Prompts Yet',
'chat.favorites.empty.description':
'Start by sending a prompt and star it or star a prompt from previous chats',
'chat.favorites.remove': 'Remove favorite'
};
return trans[key] || key;
}
})
}));
vi.mock('@chainlit/react-client', async () => {
const { atom } = await import('recoil');
return {
useConfig: vi.fn(),
useChatInteract: () => ({
toggleMessageFavorite: toggleMessageFavoriteMock
}),
favoriteMessagesState: atom({
key: 'favoriteMessagesState',
default: []
})
};
});
global.ResizeObserver = class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
};
window.HTMLElement.prototype.scrollIntoView = vi.fn();
describe('FavoriteButton', () => {
const mockOnSelect = vi.fn();
const mockFavorites: IStep[] = [
{
id: 'msg_1',
output: 'How do I center a div?',
createdAt: new Date('2023-10-01').getTime(),
type: 'assistant_message',
name: 'Assistant'
},
{
id: 'msg_2',
output: 'Explain Quantum Physics',
createdAt: new Date('2023-10-05').getTime(),
type: 'assistant_message',
name: 'Assistant'
}
];
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
const renderComponent = (favorites = mockFavorites, props = {}) => {
return render(
<RecoilRoot
initializeState={({ set }) => {
set(favoriteMessagesState, favorites);
}}
>
<FavoriteButton onSelect={mockOnSelect} {...props} />
</RecoilRoot>
);
};
it('returns null if the "favorites" feature is disabled in config', () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: false } }
});
const { container } = renderComponent();
expect(container.firstChild).toBeNull();
});
it('renders the button with empty state when there are no favorites', () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent([]);
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
// Click to open popover
fireEvent.click(button);
// Verify empty state message appears
expect(screen.getByText('No Saved Prompts Yet')).toBeInTheDocument();
expect(
screen.getByText(
'Start by sending a prompt and star it or star a prompt from previous chats'
)
).toBeInTheDocument();
});
it('shows empty state message when popover is opened with no favorites', () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent([]);
const button = screen.getByRole('button');
fireEvent.click(button);
// Empty state should be visible
const emptyTitle = screen.getByText('No Saved Prompts Yet');
const emptyDescription = screen.getByText(
'Start by sending a prompt and star it or star a prompt from previous chats'
);
expect(emptyTitle).toBeInTheDocument();
expect(emptyDescription).toBeInTheDocument();
// Regular favorites list heading should not be visible
expect(screen.queryByText('Favorites List')).not.toBeInTheDocument();
});
it('renders the button when feature is enabled and favorites exist', () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent();
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
expect(button.querySelector('svg')).toBeInTheDocument();
});
it('opens the popover and displays the list of favorites when clicked', () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent();
const button = screen.getByRole('button');
fireEvent.click(button);
expect(screen.getByText('Favorites List')).toBeInTheDocument();
expect(screen.getByText('How do I center a div?')).toBeInTheDocument();
expect(screen.getByText('Explain Quantum Physics')).toBeInTheDocument();
expect(
screen.getByText(new Date('2023-10-01').toLocaleDateString())
).toBeInTheDocument();
});
it('triggers onSelect with the correct output when an item is clicked', () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent();
const button = screen.getByRole('button');
fireEvent.click(button);
const item = screen.getByText('How do I center a div?');
fireEvent.click(item);
expect(mockOnSelect).toHaveBeenCalledTimes(1);
expect(mockOnSelect).toHaveBeenCalledWith('How do I center a div?');
});
it('renders favorites with duplicate text independently', () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
const duplicates: IStep[] = [
{
id: 'msg_dup_1',
output: 'Same prompt',
createdAt: new Date('2023-10-02').getTime(),
type: 'assistant_message',
name: 'Assistant'
},
{
id: 'msg_dup_2',
output: 'Same prompt',
createdAt: new Date('2023-10-03').getTime(),
type: 'assistant_message',
name: 'Assistant'
}
];
renderComponent(duplicates);
const button = screen.getByRole('button');
fireEvent.click(button);
const duplicateEntries = screen.getAllByText('Same prompt');
expect(duplicateEntries).toHaveLength(2);
});
it('removes a favorite without selecting the item', () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent();
const button = screen.getByRole('button');
fireEvent.click(button);
const removeButtons = screen.getAllByLabelText('Remove favorite');
fireEvent.click(removeButtons[0]);
expect(toggleMessageFavoriteMock).toHaveBeenCalledTimes(1);
expect(toggleMessageFavoriteMock).toHaveBeenCalledWith(mockFavorites[0]);
expect(mockOnSelect).not.toHaveBeenCalled();
});
it('respects the disabled prop', () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent(mockFavorites, { disabled: true });
const button = screen.getByRole('button');
expect(button).toBeDisabled();
});
it('shows tooltip text after delay on hover', async () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent();
const button = screen.getByRole('button');
fireEvent.mouseEnter(button);
expect(screen.queryByText('Use favorite')).not.toBeInTheDocument();
act(() => {
vi.advanceTimersByTime(800);
});
const tooltips = screen.getAllByText('Use favorite');
expect(tooltips.length).toBeGreaterThan(0);
});
it('cancels tooltip if mouse leaves before delay', async () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent();
const button = screen.getByRole('button');
fireEvent.mouseEnter(button);
fireEvent.mouseLeave(button);
act(() => {
vi.advanceTimersByTime(800);
});
expect(screen.queryByText('Use favorite')).not.toBeInTheDocument();
});
it('hides the tooltip instantly when the popover opens', async () => {
(useConfig as any).mockReturnValue({
config: { features: { favorites: true } }
});
renderComponent();
const button = screen.getByRole('button');
fireEvent.mouseEnter(button);
act(() => {
vi.advanceTimersByTime(800);
});
expect(screen.getAllByText('Use favorite').length).toBeGreaterThan(0);
fireEvent.click(button);
expect(screen.queryByText('Use favorite')).not.toBeInTheDocument();
expect(screen.getByText('Favorites List')).toBeInTheDocument();
});
});
+112
View File
@@ -0,0 +1,112 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import NewChatButton from '@/components/header/NewChat';
const mockClear = vi.fn();
const mockUseConfig = vi.fn();
vi.mock('@chainlit/react-client', () => ({
useChatInteract: () => ({ clear: mockClear }),
useConfig: () => mockUseConfig()
}));
vi.mock('@/components/i18n', () => ({
Translator: ({ path }: { path: string }) => <span>{path}</span>
}));
describe('NewChatButton', () => {
beforeEach(() => {
vi.clearAllMocks();
mockUseConfig.mockReturnValue({ config: {} });
});
it('renders the button correctly', () => {
render(<NewChatButton />);
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
expect(button.querySelector('svg')).toBeInTheDocument();
});
it('opens dialog by default when config is undefined', () => {
mockUseConfig.mockReturnValue({ config: undefined });
render(<NewChatButton />);
fireEvent.click(screen.getByRole('button'));
expect(
screen.getByText('navigation.newChat.dialog.title')
).toBeInTheDocument();
});
it('opens dialog by default when ui config is missing', () => {
mockUseConfig.mockReturnValue({ config: { project: {} } });
render(<NewChatButton />);
fireEvent.click(screen.getByRole('button'));
expect(
screen.getByText('navigation.newChat.dialog.title')
).toBeInTheDocument();
});
it('clears chat and navigates when confirmed via Dialog', () => {
mockUseConfig.mockReturnValue({ config: {} });
const mockNavigate = vi.fn();
render(<NewChatButton navigate={mockNavigate} />);
fireEvent.click(screen.getByRole('button'));
const confirmBtn = screen.getByText('common.actions.confirm');
fireEvent.click(confirmBtn);
expect(mockClear).toHaveBeenCalledTimes(1);
expect(mockNavigate).toHaveBeenCalledWith('/');
});
it('skips dialog and activates immediately when confirm_new_chat is false', () => {
mockUseConfig.mockReturnValue({
config: { ui: { confirm_new_chat: false } }
});
const mockNavigate = vi.fn();
render(<NewChatButton navigate={mockNavigate} />);
fireEvent.click(screen.getByRole('button'));
expect(
screen.queryByText('navigation.newChat.dialog.title')
).not.toBeInTheDocument();
expect(mockClear).toHaveBeenCalledTimes(1);
expect(mockNavigate).toHaveBeenCalledWith('/');
});
it('opens dialog explicitly when confirm_new_chat is true', () => {
mockUseConfig.mockReturnValue({
config: { ui: { confirm_new_chat: true } }
});
render(<NewChatButton />);
fireEvent.click(screen.getByRole('button'));
expect(
screen.getByText('navigation.newChat.dialog.title')
).toBeInTheDocument();
expect(mockClear).not.toHaveBeenCalled();
});
it('uses custom onConfirm handler if provided', () => {
mockUseConfig.mockReturnValue({
config: { ui: { confirm_new_chat: false } }
});
const customOnConfirm = vi.fn();
render(<NewChatButton onConfirm={customOnConfirm} />);
fireEvent.click(screen.getByRole('button'));
expect(customOnConfirm).toHaveBeenCalledTimes(1);
expect(mockClear).not.toHaveBeenCalled();
});
});
+106
View File
@@ -0,0 +1,106 @@
import { render } from '@testing-library/react';
import { expect, it } from 'vitest';
// Import the toBeInTheDocument function
import type { ITextElement } from '@chainlit/react-client';
import { MessageContent } from 'components/chat/Messages/Message/Content';
it('renders the message content', () => {
const { getByText } = render(
<MessageContent
message={{
threadId: 'test',
type: 'assistant_message',
output: 'Hello World',
id: 'test',
name: 'User',
createdAt: 0
}}
elements={[]}
/>
);
expect(getByText('Hello World')).toBeInTheDocument();
});
it('highlights multiple sources correctly (no substring matching)', () => {
const { getByRole } = render(
<MessageContent
message={{
threadId: 'test',
type: 'assistant_message',
output: `Hello world source_121, source_1, source_12`,
id: 'test2',
name: 'Test',
createdAt: 0
}}
elements={[
{
name: 'source_1',
type: 'text',
display: 'side',
url: 'source_1',
forId: 'test2'
} as ITextElement,
{
name: 'source_12',
display: 'side',
type: 'text',
forId: 'test2',
url: 'hi'
} as ITextElement,
{
name: 'source_121',
display: 'side',
type: 'text',
forId: 'test2',
url: 'hi'
} as ITextElement
]}
/>
);
expect(getByRole('link', { name: 'source_1' })).toBeInTheDocument();
expect(getByRole('link', { name: 'source_12' })).toBeInTheDocument();
expect(getByRole('link', { name: 'source_121' })).toBeInTheDocument();
});
it('highlights sources containing regex characters correctly', () => {
const { getByRole } = render(
<MessageContent
message={{
threadId: 'test',
type: 'assistant_message',
output: `Hello world: Document[1], source(12), page{12}`,
id: 'test2',
name: 'Test',
createdAt: 0
}}
elements={[
{
name: 'Document[1]',
display: 'side',
type: 'text',
url: 'hi',
forId: 'test2'
} as ITextElement,
{
name: 'source(12)',
display: 'side',
type: 'text',
url: 'hi',
forId: 'test2'
} as ITextElement,
{
name: 'page{12}',
display: 'side',
type: 'text',
url: 'hi',
forId: 'test2'
} as ITextElement
]}
/>
);
expect(getByRole('link', { name: 'Document[1]' })).toBeInTheDocument();
expect(getByRole('link', { name: 'source(12)' })).toBeInTheDocument();
expect(getByRole('link', { name: 'page{12}' })).toBeInTheDocument();
});
@@ -0,0 +1,26 @@
import { beforeEach, describe, expect, it } from 'vitest';
import {
LS_DISPLAY_MODE_KEY,
resolveDisplayMode
} from '../../libs/copilot/src/resolveDisplayMode';
describe('resolveDisplayMode config vs localStorage precedence', () => {
beforeEach(() => {
localStorage.removeItem(LS_DISPLAY_MODE_KEY);
});
it('explicit config wins over localStorage', () => {
localStorage.setItem(LS_DISPLAY_MODE_KEY, 'floating');
expect(resolveDisplayMode('sidebar')).toBe('sidebar');
});
it('falls back to localStorage when config omits displayMode', () => {
localStorage.setItem(LS_DISPLAY_MODE_KEY, 'sidebar');
expect(resolveDisplayMode(undefined)).toBe('sidebar');
});
it('defaults to floating when neither config nor localStorage is set', () => {
expect(resolveDisplayMode(undefined)).toBe('floating');
});
});
+53
View File
@@ -0,0 +1,53 @@
import { render } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import Icon from '@/components/Icon';
describe('Icon component', () => {
it('renders icon with kebab-case name', () => {
const { container } = render(<Icon name="chevron-right" />);
expect(container.querySelector('svg')).toBeInTheDocument();
});
it('renders icon with lowercase name', () => {
const { container } = render(<Icon name="plus" />);
expect(container.querySelector('svg')).toBeInTheDocument();
});
it('renders icon with PascalCase name', () => {
const { container } = render(<Icon name="ChevronRight" />);
expect(container.querySelector('svg')).toBeInTheDocument();
});
it('renders icon with all uppercase name', () => {
const { container } = render(<Icon name="PLUS" />);
expect(container.querySelector('svg')).toBeInTheDocument();
});
it('renders icon with mixed case name', () => {
const { container } = render(<Icon name="ChEvRoN-rIgHt" />);
expect(container.querySelector('svg')).toBeInTheDocument();
});
it('returns null and warns for invalid icon name', () => {
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const { container } = render(<Icon name="invalid-icon-name" />);
expect(container.firstChild).toBeNull();
expect(consoleSpy).toHaveBeenCalledWith(
'Icon "invalid-icon-name" not found in Lucide icons'
);
consoleSpy.mockRestore();
});
it('passes props to the icon component', () => {
const { container } = render(
<Icon name="home" size={24} color="red" className="test-class" />
);
const svg = container.querySelector('svg');
expect(svg).toBeInTheDocument();
expect(svg).toHaveClass('test-class');
});
});
+52
View File
@@ -0,0 +1,52 @@
import matchers from '@testing-library/jest-dom/matchers';
import { cleanup } from '@testing-library/react';
import { afterEach, expect, vi } from 'vitest';
expect.extend(matchers);
// Mock URL.createObjectURL
global.URL.createObjectURL = vi.fn();
// Polyfill DOMMatrix for pdfjs-dist which requires it at import time in JSDOM
if (typeof globalThis.DOMMatrix === 'undefined') {
globalThis.DOMMatrix = class DOMMatrix {
a = 1;
b = 0;
c = 0;
d = 1;
e = 0;
f = 0;
m11 = 1;
m12 = 0;
m13 = 0;
m14 = 0;
m21 = 0;
m22 = 1;
m23 = 0;
m24 = 0;
m31 = 0;
m32 = 0;
m33 = 1;
m34 = 0;
m41 = 0;
m42 = 0;
m43 = 0;
m44 = 1;
is2D = true;
isIdentity = true;
constructor(_init?: any) {}
static fromMatrix(_other?: any): DOMMatrix {
return new DOMMatrix();
}
static fromFloat32Array(_array32: any): DOMMatrix {
return new DOMMatrix();
}
static fromFloat64Array(_array64: any): DOMMatrix {
return new DOMMatrix();
}
} as unknown as typeof DOMMatrix;
}
afterEach(() => {
cleanup();
});
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["jest", "testing-library__jest-dom"]
},
"include": ["**/*.spec.tsx"]
}