Implement utils tests

This commit is contained in:
2025-05-26 20:35:02 +02:00
parent e5569fc4a5
commit 49cac03db8
5 changed files with 526 additions and 0 deletions

View File

@@ -1,3 +1,5 @@
import type { Parent } from 'unist';
/**
* User model from the API
*/
@@ -285,3 +287,66 @@ export interface SettingsAction<T> {
type: SettingsActionType;
payload?: T;
}
// WikiLinks
/**
* Represents a wiki link match from the regex
*/
export interface WikiLinkMatch {
fullMatch: string;
isImage: boolean; // Changed from string to boolean
fileName: string;
displayText: string;
heading?: string | undefined;
index: number;
}
/**
* Node replacement information for processing
*/
export interface ReplacementInfo {
matches: WikiLinkMatch[];
parent: Parent;
index: number;
}
/**
* Properties for link nodes
*/
export interface LinkNodeProps {
style?: {
color?: string;
textDecoration?: string;
};
}
/**
* Link node with data properties
*/
export interface LinkNode extends Node {
type: 'link';
url: string;
children: Node[];
data?: {
hProperties?: LinkNodeProps;
};
}
/**
* Image node
*/
export interface ImageNode extends Node {
type: 'image';
url: string;
alt?: string;
title?: string;
}
/**
* Text node
*/
export interface TextNode extends Node {
type: 'text';
value: string;
}

View File

@@ -0,0 +1,169 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { isImageFile, getFileUrl } from './fileHelpers';
describe('fileHelpers', () => {
beforeEach(() => {
// Ensure API_BASE_URL is set for tests
window.API_BASE_URL = 'http://localhost:8080/api/v1';
});
describe('isImageFile', () => {
it('returns true for supported image file extensions', () => {
expect(isImageFile('image.jpg')).toBe(true);
expect(isImageFile('image.jpeg')).toBe(true);
expect(isImageFile('image.png')).toBe(true);
expect(isImageFile('image.gif')).toBe(true);
expect(isImageFile('image.webp')).toBe(true);
expect(isImageFile('image.svg')).toBe(true);
});
it('returns true for uppercase image file extensions', () => {
expect(isImageFile('image.JPG')).toBe(true);
expect(isImageFile('image.JPEG')).toBe(true);
expect(isImageFile('image.PNG')).toBe(true);
expect(isImageFile('image.GIF')).toBe(true);
expect(isImageFile('image.WEBP')).toBe(true);
expect(isImageFile('image.SVG')).toBe(true);
});
it('returns true for mixed case image file extensions', () => {
expect(isImageFile('image.JpG')).toBe(true);
expect(isImageFile('image.JpEg')).toBe(true);
expect(isImageFile('image.PnG')).toBe(true);
expect(isImageFile('screenshot.WeBp')).toBe(true);
});
it('returns false for non-image file extensions', () => {
expect(isImageFile('document.md')).toBe(false);
expect(isImageFile('document.txt')).toBe(false);
expect(isImageFile('document.pdf')).toBe(false);
expect(isImageFile('document.docx')).toBe(false);
expect(isImageFile('script.js')).toBe(false);
expect(isImageFile('style.css')).toBe(false);
expect(isImageFile('data.json')).toBe(false);
expect(isImageFile('archive.zip')).toBe(false);
});
it('returns false for files without extensions', () => {
expect(isImageFile('README')).toBe(false);
expect(isImageFile('Dockerfile')).toBe(false);
expect(isImageFile('LICENSE')).toBe(false);
expect(isImageFile('Makefile')).toBe(false);
});
it('handles complex file paths correctly', () => {
expect(isImageFile('path/to/image.jpg')).toBe(true);
expect(isImageFile('./relative/path/image.png')).toBe(true);
expect(isImageFile('/absolute/path/image.gif')).toBe(true);
expect(isImageFile('../../parent/image.svg')).toBe(true);
expect(isImageFile('path/to/document.md')).toBe(false);
expect(isImageFile('./config/settings.json')).toBe(false);
});
it('handles files with multiple dots in filename', () => {
expect(isImageFile('my.image.file.jpg')).toBe(true);
expect(isImageFile('config.backup.json')).toBe(false);
expect(isImageFile('version.1.2.png')).toBe(true);
expect(isImageFile('app.config.local.js')).toBe(false);
expect(isImageFile('test.component.spec.ts')).toBe(false);
});
it('handles edge cases', () => {
expect(isImageFile('')).toBe(false);
expect(isImageFile('.')).toBe(false);
expect(isImageFile('.jpg')).toBe(true);
expect(isImageFile('.hidden.png')).toBe(true);
expect(isImageFile('file.')).toBe(false);
});
});
describe('getFileUrl', () => {
it('constructs correct file URL with simple parameters', () => {
const workspaceName = 'my-workspace';
const filePath = 'folder/file.md';
const expectedUrl =
'http://localhost:8080/api/v1/workspaces/my-workspace/files/folder%2Ffile.md';
const actualUrl = getFileUrl(workspaceName, filePath);
expect(actualUrl).toBe(expectedUrl);
});
it('properly encodes workspace name with special characters', () => {
const workspaceName = 'my workspace with spaces';
const filePath = 'file.md';
const expectedUrl =
'http://localhost:8080/api/v1/workspaces/my%20workspace%20with%20spaces/files/file.md';
const actualUrl = getFileUrl(workspaceName, filePath);
expect(actualUrl).toBe(expectedUrl);
});
it('properly encodes file path with special characters', () => {
const workspaceName = 'workspace';
const filePath = 'folder with spaces/file with spaces.md';
const expectedUrl =
'http://localhost:8080/api/v1/workspaces/workspace/files/folder%20with%20spaces%2Ffile%20with%20spaces.md';
const actualUrl = getFileUrl(workspaceName, filePath);
expect(actualUrl).toBe(expectedUrl);
});
it('handles special URL characters that need encoding', () => {
const workspaceName = 'test&workspace';
const filePath = 'file?name=test.md';
const expectedUrl =
'http://localhost:8080/api/v1/workspaces/test%26workspace/files/file%3Fname%3Dtest.md';
const actualUrl = getFileUrl(workspaceName, filePath);
expect(actualUrl).toBe(expectedUrl);
});
it('handles Unicode characters', () => {
const workspaceName = 'プロジェクト';
const filePath = 'ファイル.md';
const expectedUrl =
'http://localhost:8080/api/v1/workspaces/%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88/files/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md';
const actualUrl = getFileUrl(workspaceName, filePath);
expect(actualUrl).toBe(expectedUrl);
});
it('handles nested folder structures', () => {
const workspaceName = 'docs';
const filePath = 'projects/2024/q1/report.md';
const expectedUrl =
'http://localhost:8080/api/v1/workspaces/docs/files/projects%2F2024%2Fq1%2Freport.md';
const actualUrl = getFileUrl(workspaceName, filePath);
expect(actualUrl).toBe(expectedUrl);
});
it('handles edge cases with empty strings', () => {
expect(getFileUrl('', '')).toBe(
'http://localhost:8080/api/v1/workspaces//files/'
);
expect(getFileUrl('workspace', '')).toBe(
'http://localhost:8080/api/v1/workspaces/workspace/files/'
);
expect(getFileUrl('', 'file.md')).toBe(
'http://localhost:8080/api/v1/workspaces//files/file.md'
);
});
it('uses the API base URL correctly', () => {
// Test that the function uses the expected API base URL
// Note: The API_BASE_URL is imported at module load time, so we test the expected behavior
const url = getFileUrl('test', 'file.md');
expect(url).toBe(
'http://localhost:8080/api/v1/workspaces/test/files/file.md'
);
expect(url).toContain(window.API_BASE_URL);
});
});
});

View File

@@ -0,0 +1,124 @@
import { describe, it, expect } from 'vitest';
import { formatBytes } from './formatBytes';
describe('formatBytes', () => {
describe('bytes formatting', () => {
it('formats small byte values correctly', () => {
expect(formatBytes(0)).toBe('0.0 B');
expect(formatBytes(1)).toBe('1.0 B');
expect(formatBytes(512)).toBe('512.0 B');
expect(formatBytes(1023)).toBe('1023.0 B');
});
});
describe('kilobytes formatting', () => {
it('formats kilobyte values correctly', () => {
expect(formatBytes(1024)).toBe('1.0 KB');
expect(formatBytes(1536)).toBe('1.5 KB');
expect(formatBytes(2048)).toBe('2.0 KB');
expect(formatBytes(5120)).toBe('5.0 KB');
expect(formatBytes(1048575)).toBe('1024.0 KB'); // Just under 1MB
});
it('handles fractional kilobytes', () => {
expect(formatBytes(1433)).toBe('1.4 KB');
expect(formatBytes(1587)).toBe('1.5 KB');
expect(formatBytes(1741)).toBe('1.7 KB');
});
});
describe('megabytes formatting', () => {
it('formats megabyte values correctly', () => {
expect(formatBytes(1048576)).toBe('1.0 MB'); // 1024^2
expect(formatBytes(1572864)).toBe('1.5 MB');
expect(formatBytes(2097152)).toBe('2.0 MB');
expect(formatBytes(5242880)).toBe('5.0 MB');
expect(formatBytes(1073741823)).toBe('1024.0 MB'); // Just under 1GB
});
it('handles fractional megabytes', () => {
expect(formatBytes(1638400)).toBe('1.6 MB');
expect(formatBytes(2621440)).toBe('2.5 MB');
expect(formatBytes(10485760)).toBe('10.0 MB');
});
});
describe('gigabytes formatting', () => {
it('formats gigabyte values correctly', () => {
expect(formatBytes(1073741824)).toBe('1.0 GB'); // 1024^3
expect(formatBytes(1610612736)).toBe('1.5 GB');
expect(formatBytes(2147483648)).toBe('2.0 GB');
expect(formatBytes(5368709120)).toBe('5.0 GB');
});
it('handles fractional gigabytes', () => {
expect(formatBytes(1288490188.8)).toBe('1.2 GB');
expect(formatBytes(3221225472)).toBe('3.0 GB');
expect(formatBytes(10737418240)).toBe('10.0 GB');
});
it('handles very large gigabyte values', () => {
expect(formatBytes(1099511627776)).toBe('1024.0 GB'); // 1TB but capped at GB
expect(formatBytes(2199023255552)).toBe('2048.0 GB'); // 2TB but capped at GB
});
});
describe('decimal precision', () => {
it('always shows one decimal place', () => {
expect(formatBytes(1024)).toBe('1.0 KB');
expect(formatBytes(1536)).toBe('1.5 KB');
expect(formatBytes(1048576)).toBe('1.0 MB');
expect(formatBytes(1073741824)).toBe('1.0 GB');
});
it('rounds to one decimal place correctly', () => {
expect(formatBytes(1126)).toBe('1.1 KB'); // 1126 / 1024 = 1.099...
expect(formatBytes(1177)).toBe('1.1 KB'); // 1177 / 1024 = 1.149...
expect(formatBytes(1229)).toBe('1.2 KB'); // 1229 / 1024 = 1.200...
});
});
describe('edge cases', () => {
it('handles exact unit boundaries', () => {
expect(formatBytes(1024)).toBe('1.0 KB');
expect(formatBytes(1048576)).toBe('1.0 MB');
expect(formatBytes(1073741824)).toBe('1.0 GB');
});
it('handles very small decimal values', () => {
expect(formatBytes(0.1)).toBe('0.1 B');
expect(formatBytes(0.9)).toBe('0.9 B');
});
it('handles negative values (edge case)', () => {
expect(() => formatBytes(-1024)).toThrowError(
'Byte size cannot be negative'
);
expect(() => formatBytes(-1048576)).toThrowError(
'Byte size cannot be negative'
);
});
it('handles extremely large values', () => {
const largeValue = Number.MAX_SAFE_INTEGER;
const result = formatBytes(largeValue);
expect(result).toContain('GB');
expect(result).toMatch(/^\d+\.\d GB$/);
});
});
describe('unit progression', () => {
it('uses the correct unit for each range', () => {
expect(formatBytes(500)).toContain('B');
expect(formatBytes(5000)).toContain('KB');
expect(formatBytes(5000000)).toContain('MB');
expect(formatBytes(5000000000)).toContain('GB');
});
it('stops at GB unit (does not go to TB)', () => {
const oneTerabyte = 1024 * 1024 * 1024 * 1024;
expect(formatBytes(oneTerabyte)).toContain('GB');
expect(formatBytes(oneTerabyte)).not.toContain('TB');
});
});
});

View File

@@ -16,6 +16,9 @@ const UNITS: readonly ByteUnit[] = ['B', 'KB', 'MB', 'GB'] as const;
export const formatBytes = (bytes: number): string => {
let size: number = bytes;
let unitIndex: number = 0;
if (size < 0) {
throw new Error('Byte size cannot be negative');
}
while (size >= 1024 && unitIndex < UNITS.length - 1) {
size /= 1024;
unitIndex++;

View File

@@ -0,0 +1,165 @@
import { describe, it, expect } from 'vitest';
import type { MantineTheme } from '@mantine/core';
import {
getHoverStyle,
getConditionalColor,
getAccordionStyles,
getWorkspacePaperStyle,
getTextColor,
} from './themeStyles';
// Create partial mock themes with only the properties we need
const createMockTheme = (colorScheme: 'light' | 'dark') => ({
radius: { sm: '4px' },
spacing: { md: '16px' },
colors: {
dark: [
'#fff',
'#f8f9fa',
'#e9ecef',
'#dee2e6',
'#ced4da',
'#adb5bd',
'#6c757d',
'#495057',
'#343a40',
'#212529',
],
gray: [
'#f8f9fa',
'#e9ecef',
'#dee2e6',
'#ced4da',
'#adb5bd',
'#6c757d',
'#495057',
'#343a40',
'#212529',
'#000',
],
blue: [
'#e7f5ff',
'#d0ebff',
'#a5d8ff',
'#74c0fc',
'#339af0',
'#228be6',
'#1971c2',
'#1864ab',
'#0b4fa8',
'#073e78',
],
},
colorScheme,
});
const mockLightTheme = createMockTheme('light') as unknown as MantineTheme;
const mockDarkTheme = createMockTheme('dark') as unknown as MantineTheme;
describe('themeStyles utilities', () => {
describe('getHoverStyle', () => {
it('returns correct hover styles for light theme', () => {
const result = getHoverStyle(mockLightTheme);
expect(result).toEqual({
borderRadius: '4px',
'&:hover': {
backgroundColor: '#f8f9fa', // gray[0] for light theme
},
});
});
it('returns correct hover styles for dark theme', () => {
const result = getHoverStyle(mockDarkTheme);
expect(result).toEqual({
borderRadius: '4px',
'&:hover': {
backgroundColor: '#adb5bd', // dark[5] for dark theme
},
});
});
});
describe('getConditionalColor', () => {
it('returns blue color when selected in light theme', () => {
const result = getConditionalColor(mockLightTheme, true);
expect(result).toBe('#1864ab'); // blue[7] for light theme
});
it('returns blue color when selected in dark theme', () => {
const result = getConditionalColor(mockDarkTheme, true);
expect(result).toBe('#a5d8ff'); // blue[2] for dark theme
});
it('returns dimmed when not selected', () => {
expect(getConditionalColor(mockLightTheme, false)).toBe('dimmed');
expect(getConditionalColor(mockDarkTheme, false)).toBe('dimmed');
});
it('defaults to dimmed when no selection parameter provided', () => {
expect(getConditionalColor(mockLightTheme)).toBe('dimmed');
expect(getConditionalColor(mockDarkTheme)).toBe('dimmed');
});
});
describe('getAccordionStyles', () => {
it('returns correct accordion styles for light theme', () => {
const result = getAccordionStyles(mockLightTheme);
expect(result.control.paddingTop).toBe('16px');
expect(result.control.paddingBottom).toBe('16px');
expect(result.item.borderBottom).toBe('1px solid #ced4da'); // gray[3]
expect(result.item['&[data-active]'].backgroundColor).toBe('#f8f9fa'); // gray[0]
});
it('returns correct accordion styles for dark theme', () => {
const result = getAccordionStyles(mockDarkTheme);
expect(result.control.paddingTop).toBe('16px');
expect(result.control.paddingBottom).toBe('16px');
expect(result.item.borderBottom).toBe('1px solid #ced4da'); // dark[4]
expect(result.item['&[data-active]'].backgroundColor).toBe('#495057'); // dark[7]
});
});
describe('getWorkspacePaperStyle', () => {
it('returns selected styles for light theme when selected', () => {
const result = getWorkspacePaperStyle(mockLightTheme, true);
expect(result.backgroundColor).toBe('#d0ebff'); // blue[1]
expect(result.borderColor).toBe('#228be6'); // blue[5]
});
it('returns selected styles for dark theme when selected', () => {
const result = getWorkspacePaperStyle(mockDarkTheme, true);
expect(result.backgroundColor).toBe('#0b4fa8'); // blue[8]
expect(result.borderColor).toBe('#1864ab'); // blue[7]
});
it('returns undefined styles when not selected', () => {
const result = getWorkspacePaperStyle(mockLightTheme, false);
expect(result.backgroundColor).toBeUndefined();
expect(result.borderColor).toBeUndefined();
});
});
describe('getTextColor', () => {
it('returns blue text color when selected in light theme', () => {
const result = getTextColor(mockLightTheme, true);
expect(result).toBe('#073e78'); // blue[9]
});
it('returns blue text color when selected in dark theme', () => {
const result = getTextColor(mockDarkTheme, true);
expect(result).toBe('#e7f5ff'); // blue[0]
});
it('returns null when not selected', () => {
expect(getTextColor(mockLightTheme, false)).toBeNull();
expect(getTextColor(mockDarkTheme, false)).toBeNull();
});
});
});