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
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:
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["jest", "testing-library__jest-dom"]
|
||||
},
|
||||
"include": ["**/*.spec.tsx"]
|
||||
}
|
||||
Reference in New Issue
Block a user