mirror of
https://github.com/ByteAtATime/raycast-linux.git
synced 2025-08-31 03:07:23 +00:00
test(frontend): add vitest for unit and component testing
This commit introduces a complete testing setup using Vitest. It's configured it to handle both unit tests in a Node environment and component tests using JSDOM. The `vite.config.js` now defines two separate test projects ('client' and 'server') to manage these different environments. Lastly, to ensure the testing environment is setup correctly, it adds tests for the CommandPalette extension
This commit is contained in:
parent
4099f7dd9a
commit
a26cd8c3fe
10 changed files with 1412 additions and 8 deletions
16
package.json
16
package.json
|
@ -11,7 +11,9 @@
|
|||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"tauri": "tauri",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint ."
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"test:unit": "vitest",
|
||||
"test": "npm run test:unit -- --run"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -46,15 +48,21 @@
|
|||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/svelte": "^5.2.8",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/pako": "^2.0.3",
|
||||
"@vitest/browser": "^3.2.3",
|
||||
"bits-ui": "^2.8.10",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-svelte": "^3.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"mode-watcher": "^1.0.8",
|
||||
"playwright": "^1.53.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
|
@ -67,11 +75,13 @@
|
|||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.20.0",
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-node-polyfills": "^0.23.0"
|
||||
"vite-plugin-node-polyfills": "^0.23.0",
|
||||
"vitest": "^3.2.3",
|
||||
"vitest-browser-svelte": "^0.1.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
895
pnpm-lock.yaml
generated
895
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
31
src/lib/__mocks__/tauri.mock.ts
Normal file
31
src/lib/__mocks__/tauri.mock.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { vi, type Mock, type MockedObject } from 'vitest';
|
||||
import * as core from '@tauri-apps/api/core';
|
||||
import * as os from '@tauri-apps/plugin-os';
|
||||
import * as clipboard from '@tauri-apps/plugin-clipboard-manager';
|
||||
|
||||
type Promisable<T> = T | Promise<T>;
|
||||
type MockFactoryWithHelper<M = unknown> = (
|
||||
importOriginal: <T extends M = M>() => Promise<T>
|
||||
) => Promisable<Partial<M>>;
|
||||
|
||||
const createApiMock: MockFactoryWithHelper = vi.hoisted(() => async (importOriginal) => {
|
||||
const module = (await importOriginal()) as object;
|
||||
|
||||
const mocks: Record<string, Mock> = {};
|
||||
for (const [key, value] of Object.entries(module)) {
|
||||
if (typeof value === 'function') {
|
||||
mocks[key] = vi.fn();
|
||||
}
|
||||
}
|
||||
|
||||
const mock = { ...module, ...mocks };
|
||||
return { ...mock, default: mock };
|
||||
});
|
||||
|
||||
vi.mock('@tauri-apps/api/core', createApiMock);
|
||||
vi.mock('@tauri-apps/plugin-os', createApiMock);
|
||||
vi.mock('@tauri-apps/plugin-clipboard-manager', createApiMock);
|
||||
|
||||
export const mockedCore = core as MockedObject<typeof core>;
|
||||
export const mockedOs = os as MockedObject<typeof os>;
|
||||
export const mockedClipboard = clipboard as MockedObject<typeof clipboard>;
|
|
@ -28,7 +28,6 @@
|
|||
return [
|
||||
{
|
||||
title: 'Copy Answer',
|
||||
shortcut: { modifiers: ['ctrl', 'shift'], key: 'c' },
|
||||
handler: barActions.handleEnter
|
||||
},
|
||||
{
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
if (item?.type === 'quicklink' && item.data.link.includes('{argument}')) {
|
||||
selectedQuicklinkForArgument = item.data;
|
||||
} else {
|
||||
console.log('null haha');
|
||||
selectedQuicklinkForArgument = null;
|
||||
}
|
||||
});
|
||||
|
|
418
src/lib/components/command-palette/CommandPalette.svelte.test.ts
Normal file
418
src/lib/components/command-palette/CommandPalette.svelte.test.ts
Normal file
|
@ -0,0 +1,418 @@
|
|||
import { mockedClipboard, mockedCore } from '$lib/__mocks__/tauri.mock';
|
||||
import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/svelte';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import CommandPalette from './CommandPalette.svelte';
|
||||
import { type PluginInfo } from '@raycast-linux/protocol';
|
||||
import type { App } from '$lib/apps.svelte';
|
||||
import type { Quicklink } from '$lib/quicklinks.svelte';
|
||||
import { focusManager } from '$lib/focus.svelte';
|
||||
|
||||
const appsStore = vi.hoisted(() => ({
|
||||
apps: [] as App[],
|
||||
isLoading: false
|
||||
}));
|
||||
vi.mock('$lib/apps.svelte', () => ({
|
||||
appsStore
|
||||
}));
|
||||
|
||||
const quicklinksStore = vi.hoisted(() => ({
|
||||
quicklinks: [] as Quicklink[],
|
||||
isLoading: false,
|
||||
error: null
|
||||
}));
|
||||
vi.mock('$lib/quicklinks.svelte', () => ({
|
||||
quicklinksStore
|
||||
}));
|
||||
|
||||
const frecencyStore = vi.hoisted(() => ({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
hiddenItemIds: [],
|
||||
recordUsage: vi.fn().mockResolvedValue(undefined),
|
||||
hideItem: vi.fn().mockResolvedValue(undefined)
|
||||
}));
|
||||
vi.mock('$lib/frecency.svelte', () => ({
|
||||
frecencyStore
|
||||
}));
|
||||
|
||||
const viewManager = vi.hoisted(() => ({
|
||||
showSettings: vi.fn()
|
||||
}));
|
||||
vi.mock('$lib/viewManager.svelte', () => ({
|
||||
viewManager
|
||||
}));
|
||||
|
||||
describe('CommandPalette.svelte', () => {
|
||||
const onRunPlugin = vi.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
const mockPlugins: PluginInfo[] = [
|
||||
{
|
||||
pluginPath: '/path/to/plugin1',
|
||||
title: 'Mock Plugin 1',
|
||||
pluginName: 'mock-plugin-1',
|
||||
pluginTitle: 'Mock Extension',
|
||||
commandName: 'mock-command-1',
|
||||
icon: 'mock-icon-16',
|
||||
mode: 'view',
|
||||
owner: 'test',
|
||||
author: 'test',
|
||||
preferences: [],
|
||||
description: 'A mock plugin'
|
||||
},
|
||||
{
|
||||
pluginPath: '/path/to/plugin2',
|
||||
title: 'Mock Plugin 2',
|
||||
pluginName: 'mock-plugin-2',
|
||||
pluginTitle: 'Another Extension',
|
||||
commandName: 'mock-command-2',
|
||||
icon: 'mock-icon-16',
|
||||
mode: 'view',
|
||||
owner: 'test',
|
||||
preferences: [],
|
||||
description: 'Another mock plugin'
|
||||
}
|
||||
];
|
||||
|
||||
const mockApps: App[] = [
|
||||
{
|
||||
name: 'Test App 1',
|
||||
comment: 'A great testing application',
|
||||
exec: '/usr/bin/test-app-1'
|
||||
}
|
||||
];
|
||||
|
||||
const mockQuicklinks: Quicklink[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Google Search',
|
||||
link: 'https://google.com/search?q={argument}',
|
||||
application: null,
|
||||
icon: 'link-16',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Simple Link',
|
||||
link: 'https://example.com',
|
||||
application: null,
|
||||
icon: 'link-16',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
cleanup();
|
||||
vi.clearAllMocks();
|
||||
appsStore.apps = [];
|
||||
quicklinksStore.quicklinks = [];
|
||||
frecencyStore.data = [];
|
||||
mockedCore.invoke.mockResolvedValue(undefined);
|
||||
focusManager.reset();
|
||||
});
|
||||
|
||||
describe('1. Initial Rendering and Display', () => {
|
||||
it('should render the search input and default placeholder', () => {
|
||||
render(CommandPalette, {
|
||||
plugins: [],
|
||||
onRunPlugin
|
||||
});
|
||||
|
||||
const input = screen.getByPlaceholderText('Search for apps and commands...');
|
||||
expect(input).toBeInTheDocument();
|
||||
|
||||
expect(input).toHaveFocus();
|
||||
});
|
||||
|
||||
it('should display a list of plugins', async () => {
|
||||
render(CommandPalette, {
|
||||
plugins: mockPlugins,
|
||||
onRunPlugin
|
||||
});
|
||||
|
||||
expect(await screen.findByText('Mock Plugin 1')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Mock Extension')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Mock Plugin 2')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Another Extension')).toBeInTheDocument();
|
||||
|
||||
const commandAccessories = await screen.findAllByText('Command');
|
||||
expect(commandAccessories.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should display a list of installed applications', async () => {
|
||||
appsStore.apps = mockApps;
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
expect(await screen.findByText('Test App 1')).toBeInTheDocument();
|
||||
expect(await screen.findByText('A great testing application')).toBeInTheDocument();
|
||||
|
||||
const appAccessories = await screen.findAllByText('Application');
|
||||
expect(appAccessories.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should display a list of quicklinks', async () => {
|
||||
quicklinksStore.quicklinks = mockQuicklinks;
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
expect(await screen.findByText('Google Search', { selector: 'p' })).toBeInTheDocument();
|
||||
expect(await screen.findByText('https://google.com/search?q=...')).toBeInTheDocument();
|
||||
|
||||
const quicklinkAccessories = await screen.findAllByText('Quicklink');
|
||||
expect(quicklinkAccessories.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('2. Search and Filtering', () => {
|
||||
it('should filter the list based on search text', async () => {
|
||||
appsStore.apps = mockApps;
|
||||
quicklinksStore.quicklinks = mockQuicklinks;
|
||||
|
||||
const { container } = render(CommandPalette, {
|
||||
plugins: mockPlugins,
|
||||
onRunPlugin
|
||||
});
|
||||
|
||||
const listContainer = container.querySelector('.grow.overflow-y-auto');
|
||||
|
||||
const initialListCount = listContainer!.getElementsByTagName('button').length;
|
||||
|
||||
const searchInput = screen.getByPlaceholderText('Search for apps and commands...');
|
||||
await user.type(searchInput, 'Mock Plugin 1');
|
||||
|
||||
expect(listContainer).toBeInTheDocument();
|
||||
|
||||
const listItems = listContainer!.getElementsByTagName('button');
|
||||
expect(listItems.length).not.toEqual(initialListCount);
|
||||
expect(listItems[0]).toHaveClass('!bg-accent');
|
||||
});
|
||||
|
||||
it('should display the calculator result when a mathematical expression is typed', async () => {
|
||||
mockedCore.invoke.mockImplementation(async (command) => {
|
||||
if (command === 'calculate_soulver') {
|
||||
return JSON.stringify({ value: '4', type: 'number' });
|
||||
}
|
||||
});
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
const searchInput = screen.getByPlaceholderText('Search for apps and commands...');
|
||||
await user.type(searchInput, '2+2');
|
||||
|
||||
expect(await screen.findByText('Calculator')).toBeInTheDocument();
|
||||
expect(screen.getByText('2+2')).toBeInTheDocument();
|
||||
expect(screen.getByText('4')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display the calculator for non-mathematical text', async () => {
|
||||
mockedCore.invoke.mockImplementation(async (command) => {
|
||||
if (command === 'calculate_soulver') {
|
||||
return JSON.stringify({ type: 'none' });
|
||||
}
|
||||
});
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
const searchInput = screen.getByPlaceholderText('Search for apps and commands...');
|
||||
await user.type(searchInput, 'hello world');
|
||||
|
||||
expect(screen.queryByText('Calculator')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should clear the search text when the Escape key is pressed in the input', async () => {
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
const searchInput = screen.getByPlaceholderText<HTMLInputElement>(
|
||||
'Search for apps and commands...'
|
||||
);
|
||||
|
||||
await user.type(searchInput, 'some text');
|
||||
expect(searchInput.value).toBe('some text');
|
||||
|
||||
await fireEvent.keyDown(searchInput, { key: 'Escape' });
|
||||
|
||||
expect(searchInput.value).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('3. Item Selection and Execution (`Enter` key)', () => {
|
||||
it('should execute a selected plugin on Enter', async () => {
|
||||
render(CommandPalette, { plugins: [mockPlugins[0]], onRunPlugin });
|
||||
|
||||
await user.keyboard('{Enter}');
|
||||
|
||||
expect(onRunPlugin).toHaveBeenCalledTimes(1);
|
||||
expect(onRunPlugin).toHaveBeenCalledWith(mockPlugins[0]);
|
||||
expect(frecencyStore.recordUsage).toHaveBeenCalledWith(mockPlugins[0].pluginPath);
|
||||
});
|
||||
|
||||
it('should launch a selected application on Enter', async () => {
|
||||
appsStore.apps = mockApps;
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
await user.keyboard('{Enter}');
|
||||
|
||||
expect(mockedCore.invoke).toHaveBeenCalledWith('launch_app', { exec: mockApps[0].exec });
|
||||
expect(frecencyStore.recordUsage).toHaveBeenCalledWith(mockApps[0].exec);
|
||||
});
|
||||
|
||||
it('should copy the calculator answer on Enter', async () => {
|
||||
mockedCore.invoke.mockImplementation(async (command) => {
|
||||
if (command === 'calculate_soulver') {
|
||||
return JSON.stringify({ value: '4', type: 'number' });
|
||||
}
|
||||
});
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
const searchInput = screen.getByPlaceholderText('Search for apps and commands...');
|
||||
await user.type(searchInput, '2+2');
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Calculator')).toBeInTheDocument());
|
||||
|
||||
await user.keyboard('{Enter}');
|
||||
|
||||
expect(mockedClipboard.writeText).toHaveBeenCalledWith('4');
|
||||
});
|
||||
|
||||
it('should execute a simple quicklink (without argument) on Enter', async () => {
|
||||
const simpleQuicklink = mockQuicklinks.find((q) => !q.link.includes('{argument}'))!;
|
||||
quicklinksStore.quicklinks = [simpleQuicklink];
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
await waitFor(() => expect(screen.getByText(simpleQuicklink.name)).toBeInTheDocument());
|
||||
|
||||
await user.keyboard('{Enter}');
|
||||
|
||||
expect(mockedCore.invoke).toHaveBeenCalledWith('execute_quicklink', {
|
||||
link: simpleQuicklink.link,
|
||||
application: simpleQuicklink.application
|
||||
});
|
||||
expect(frecencyStore.recordUsage).toHaveBeenCalledWith(`quicklink-${simpleQuicklink.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('4. Quicklinks with Arguments', () => {
|
||||
it('should show the argument input when a complex quicklink is selected', async () => {
|
||||
const complexQuicklink = mockQuicklinks.find((q) => q.link.includes('{argument}'))!;
|
||||
quicklinksStore.quicklinks = [complexQuicklink];
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
const argumentInput = await screen.findByPlaceholderText('Query');
|
||||
expect(argumentInput).toBeInTheDocument();
|
||||
|
||||
await user.keyboard('{Enter}');
|
||||
await waitFor(() => expect(argumentInput).toHaveFocus());
|
||||
|
||||
const mainInput = screen.getByPlaceholderText(complexQuicklink.name);
|
||||
expect(mainInput).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should execute a complex quicklink with the provided argument', async () => {
|
||||
const complexQuicklink = mockQuicklinks.find((q) => q.link.includes('{argument}'))!;
|
||||
quicklinksStore.quicklinks = [complexQuicklink];
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
const argumentInput = await screen.findByPlaceholderText('Query');
|
||||
expect(argumentInput).toBeInTheDocument();
|
||||
|
||||
await user.keyboard('{Enter}');
|
||||
expect(argumentInput).toHaveFocus();
|
||||
|
||||
await user.type(argumentInput, 'Svelte');
|
||||
await user.keyboard('{Enter}');
|
||||
|
||||
expect(mockedCore.invoke).toHaveBeenCalledWith('execute_quicklink', {
|
||||
link: 'https://google.com/search?q=Svelte',
|
||||
application: complexQuicklink.application
|
||||
});
|
||||
|
||||
const mainInput = screen.getByPlaceholderText('Search for apps and commands...');
|
||||
expect(mainInput).toHaveValue('');
|
||||
});
|
||||
|
||||
it('should hide the argument input on Escape', async () => {
|
||||
const complexQuicklink = mockQuicklinks.find((q) => q.link.includes('{argument}'))!;
|
||||
quicklinksStore.quicklinks = [complexQuicklink];
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
const argumentInput = await screen.findByPlaceholderText('Query');
|
||||
await user.keyboard('{Enter}');
|
||||
await waitFor(() => expect(argumentInput).toHaveFocus());
|
||||
|
||||
await user.keyboard('{Escape}');
|
||||
|
||||
const mainInput = screen.getByPlaceholderText(complexQuicklink.name);
|
||||
expect(mainInput).toHaveFocus();
|
||||
});
|
||||
|
||||
it('should hide the argument input on Backspace in an empty argument input', async () => {
|
||||
const complexQuicklink = mockQuicklinks.find((q) => q.link.includes('{argument}'))!;
|
||||
quicklinksStore.quicklinks = [complexQuicklink];
|
||||
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
|
||||
const argumentInput = await screen.findByPlaceholderText('Query');
|
||||
await user.keyboard('{Enter}');
|
||||
await waitFor(() => expect(argumentInput).toHaveFocus());
|
||||
|
||||
expect(argumentInput).toHaveValue('');
|
||||
await user.keyboard('{Backspace}');
|
||||
|
||||
const mainInput = screen.getByPlaceholderText(complexQuicklink.name);
|
||||
expect(mainInput).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. Keyboard Shortcuts and Actions', () => {
|
||||
it('should trigger "Copy Deeplink" with Ctrl+Shift+C for a plugin', async () => {
|
||||
render(CommandPalette, { plugins: [mockPlugins[0]], onRunPlugin });
|
||||
const searchInput = await screen.findByPlaceholderText('Search for apps and commands...');
|
||||
await waitFor(() => expect(screen.getByText('Mock Plugin 1')).toBeInTheDocument());
|
||||
|
||||
await fireEvent.keyDown(searchInput, { key: 'c', ctrlKey: true, shiftKey: true });
|
||||
|
||||
expect(mockedClipboard.writeText).toHaveBeenCalledWith(
|
||||
'raycast://extensions/test/mock-plugin-1/mock-command-1'
|
||||
);
|
||||
});
|
||||
|
||||
it('should trigger "Copy App Path" with Ctrl+Shift+. for an app', async () => {
|
||||
appsStore.apps = mockApps;
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
const searchInput = await screen.findByPlaceholderText('Search for apps and commands...');
|
||||
await waitFor(() => expect(screen.getByText('Test App 1')).toBeInTheDocument());
|
||||
|
||||
await fireEvent.keyDown(searchInput, { key: '.', ctrlKey: true, shiftKey: true });
|
||||
|
||||
expect(mockedClipboard.writeText).toHaveBeenCalledWith('/usr/bin/test-app-1');
|
||||
});
|
||||
|
||||
it('should trigger "Hide Application" with Ctrl+h for an app', async () => {
|
||||
appsStore.apps = mockApps;
|
||||
render(CommandPalette, { plugins: [], onRunPlugin });
|
||||
const searchInput = await screen.findByPlaceholderText('Search for apps and commands...');
|
||||
await waitFor(() => expect(screen.getByText('Test App 1')).toBeInTheDocument());
|
||||
|
||||
await fireEvent.keyDown(searchInput, { key: 'h', ctrlKey: true });
|
||||
|
||||
expect(frecencyStore.hideItem).toHaveBeenCalledWith('/usr/bin/test-app-1');
|
||||
});
|
||||
|
||||
it('should trigger "Configure Command" with Ctrl+Shift+, for a plugin', async () => {
|
||||
render(CommandPalette, { plugins: [mockPlugins[0]], onRunPlugin });
|
||||
const searchInput = await screen.findByPlaceholderText('Search for apps and commands...');
|
||||
await waitFor(() => expect(screen.getByText('Mock Plugin 1')).toBeInTheDocument());
|
||||
|
||||
await fireEvent.keyDown(searchInput, { key: ',', ctrlKey: true, shiftKey: true });
|
||||
|
||||
expect(viewManager.showSettings).toHaveBeenCalledWith('mock-plugin-1');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -26,6 +26,10 @@ class FocusManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.#stack = ['main-input'];
|
||||
}
|
||||
}
|
||||
|
||||
export const focusManager = new FocusManager();
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["@testing-library/jest-dom"]
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
|
||||
|
|
|
@ -2,12 +2,13 @@ import { defineConfig, searchForWorkspaceRoot } from 'vite';
|
|||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||
import { svelteTesting } from '@testing-library/svelte/vite';
|
||||
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [tailwindcss(), sveltekit(), nodePolyfills()],
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit(), nodePolyfills(), svelteTesting()],
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
|
@ -32,5 +33,29 @@ export default defineConfig(async () => ({
|
|||
fs: {
|
||||
allow: [searchForWorkspaceRoot(process.cwd())]
|
||||
}
|
||||
},
|
||||
|
||||
test: {
|
||||
projects: [
|
||||
{
|
||||
extends: './vite.config.js',
|
||||
test: {
|
||||
name: 'client',
|
||||
environment: 'jsdom',
|
||||
include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/lib/server/**'],
|
||||
setupFiles: ['./vitest-setup-client.ts']
|
||||
}
|
||||
},
|
||||
{
|
||||
extends: './vite.config.js',
|
||||
test: {
|
||||
name: 'server',
|
||||
environment: 'node',
|
||||
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
|
20
vitest-setup-client.ts
Normal file
20
vitest-setup-client.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import '@testing-library/jest-dom/vitest';
|
||||
import { beforeAll } from 'vitest';
|
||||
|
||||
/// <reference types="@vitest/browser/matchers" />
|
||||
/// <reference types="@vitest/browser/providers/playwright" />
|
||||
|
||||
beforeAll(() => {
|
||||
// TODO: better method?
|
||||
global.ResizeObserver = class ResizeObserver {
|
||||
observe() {
|
||||
// do nothing
|
||||
}
|
||||
unobserve() {
|
||||
// do nothing
|
||||
}
|
||||
disconnect() {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue