feat: optimize dashboard/search UX and simplify extension architecture

Redesign Dashboard with compact tool grid and recently used tools
Improve TopBar search UX with Cmd/Ctrl+K shortcut and better history navigation
Reorganize project structure into src/
Migrate i18n from react-i18next to chrome.i18n
Remove runtime language switch and settings page
Remove HTML/Markdown conversion tools
Clean up unused code, dead animations, redundant comments, and imports
Improve component consistency with shadcn/ui patterns
Replace hardcoded strings/colors with i18n tokens and theme tokens
Add comprehensive project documentation and coding standards
Fix CI artifact upload workflow and multiple TypeScript/test issues

Includes various refactors, UI polish, i18n cleanup, CI improvements,
and maintenance updates across the codebase.
This commit is contained in:
LingandRX
2026-05-28 22:39:25 +08:00
committed by GitHub
parent d4c29a1bf4
commit 945780def8
200 changed files with 1557 additions and 4380 deletions
+156
View File
@@ -0,0 +1,156 @@
import { describe, it, expect } from 'vitest';
import { formatJson, validateJson, type JsonFormatOptions } from '../jsonFormatter';
describe('formatJson', () => {
const defaultOptions: JsonFormatOptions = { indentSize: 2, sortKeys: false };
it('should format minified JSON', () => {
const input = '{"name":"test","value":123}';
const result = formatJson(input, defaultOptions);
expect(result.formatted).toBe('{\n "name": "test",\n "value": 123\n}');
expect(result.originalBytes).toBeGreaterThan(0);
expect(result.formattedBytes).toBeGreaterThan(0);
});
it('should return empty result for empty input', () => {
const result = formatJson('', defaultOptions);
expect(result.formatted).toBe('');
expect(result.originalBytes).toBe(0);
expect(result.formattedBytes).toBe(0);
});
it('should return empty result for whitespace-only input', () => {
const result = formatJson(' ', defaultOptions);
expect(result.formatted).toBe('');
});
it('should format with custom indent size', () => {
const input = '{"a":1}';
const result4 = formatJson(input, { indentSize: 4, sortKeys: false });
expect(result4.formatted).toBe('{\n "a": 1\n}');
const result8 = formatJson(input, { indentSize: 8, sortKeys: false });
expect(result8.formatted).toBe('{\n "a": 1\n}');
});
it('should sort keys alphabetically when sortKeys is true', () => {
const input = '{"c":3,"a":1,"b":2}';
const result = formatJson(input, { indentSize: 2, sortKeys: true });
expect(result.formatted).toBe('{\n "a": 1,\n "b": 2,\n "c": 3\n}');
});
it('should sort nested object keys recursively', () => {
const input = '{"z":1,"a":{"d":4,"b":2,"c":3}}';
const result = formatJson(input, { indentSize: 2, sortKeys: true });
expect(result.formatted).toBe(
'{\n "a": {\n "b": 2,\n "c": 3,\n "d": 4\n },\n "z": 1\n}',
);
});
it('should not sort keys by default', () => {
const input = '{"c":3,"a":1,"b":2}';
const result = formatJson(input, defaultOptions);
expect(result.formatted).toBe('{\n "c": 3,\n "a": 1,\n "b": 2\n}');
});
it('should handle arrays correctly', () => {
const input = '[1,2,3]';
const result = formatJson(input, defaultOptions);
expect(result.formatted).toBe('[\n 1,\n 2,\n 3\n]');
});
it('should handle nested arrays and objects', () => {
const input = '{"users":[{"name":"Alice"},{"name":"Bob"}]}';
const result = formatJson(input, defaultOptions);
expect(result.formatted).toContain('"users"');
expect(result.formatted).toContain('"Alice"');
expect(result.formatted).toContain('"Bob"');
});
it('should handle primitive values', () => {
expect(formatJson('null', defaultOptions).formatted).toBe('null');
expect(formatJson('true', defaultOptions).formatted).toBe('true');
expect(formatJson('42', defaultOptions).formatted).toBe('42');
expect(formatJson('"hello"', defaultOptions).formatted).toBe('"hello"');
});
it('should throw SyntaxError for invalid JSON', () => {
expect(() => formatJson('{invalid}', defaultOptions)).toThrow(SyntaxError);
});
it('should calculate byte sizes correctly', () => {
const input = '{"a":1}';
const result = formatJson(input, defaultOptions);
// ASCII characters: each character = 1 byte
expect(result.originalBytes).toBe(input.trim().length);
expect(result.formattedBytes).toBe(result.formatted.length);
});
it('should handle JSON with Unicode characters', () => {
const input = '{"name":"测试"}';
const result = formatJson(input, defaultOptions);
expect(result.formatted).toContain('"测试"');
// UTF-8: each Chinese character is 3 bytes
expect(result.originalBytes).toBeGreaterThan(input.trim().length);
});
it('should sort keys in arrays containing objects', () => {
const input = '[{"z":1,"a":2}]';
const result = formatJson(input, { indentSize: 2, sortKeys: true });
expect(result.formatted).toContain('"a": 2');
expect(result.formatted).toContain('"z": 1');
// Ensure sorted order
const aIndex = result.formatted.indexOf('"a": 2');
const zIndex = result.formatted.indexOf('"z": 1');
expect(aIndex).toBeLessThan(zIndex);
});
it('should handle already formatted JSON', () => {
const input = '{\n "name": "test",\n "value": 123\n}';
const result = formatJson(input, defaultOptions);
expect(result.formatted).toBe('{\n "name": "test",\n "value": 123\n}');
});
});
describe('validateJson', () => {
it('should return null for valid JSON object', () => {
expect(validateJson('{"a":1}')).toBeNull();
});
it('should return null for valid JSON array', () => {
expect(validateJson('[1,2,3]')).toBeNull();
});
it('should return null for valid JSON primitive', () => {
expect(validateJson('null')).toBeNull();
expect(validateJson('true')).toBeNull();
expect(validateJson('42')).toBeNull();
expect(validateJson('"hello"')).toBeNull();
});
it('should return null for empty input', () => {
expect(validateJson('')).toBeNull();
});
it('should return null for whitespace-only input', () => {
expect(validateJson(' ')).toBeNull();
});
it('should return error message for invalid JSON', () => {
const error = validateJson('{invalid}');
expect(error).not.toBeNull();
expect(typeof error).toBe('string');
});
it('should return error for unclosed bracket', () => {
expect(validateJson('{"a":1')).not.toBeNull();
});
it('should return error for trailing comma', () => {
expect(validateJson('{"a":1,}')).not.toBeNull();
});
it('should handle JSON with leading/trailing whitespace', () => {
expect(validateJson(' {"a":1} ')).toBeNull();
});
});