mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
style: run prettier formatter over src and tests
This commit is contained in:
+13
-2
@@ -18,7 +18,12 @@ vi.mock('@/lib/ipc', () => ({
|
||||
},
|
||||
app: { getVersion: vi.fn().mockResolvedValue({ ok: true, data: '5.0.1' }) },
|
||||
menu: { on: vi.fn(() => () => {}) },
|
||||
updater: { check: vi.fn(), install: vi.fn(), getState: vi.fn(), onStatus: vi.fn(() => () => {}) },
|
||||
updater: {
|
||||
check: vi.fn(),
|
||||
install: vi.fn(),
|
||||
getState: vi.fn(),
|
||||
onStatus: vi.fn(() => () => {}),
|
||||
},
|
||||
crash: { read: vi.fn(), openDir: vi.fn(), delete: vi.fn() },
|
||||
},
|
||||
}));
|
||||
@@ -35,7 +40,13 @@ describe('App — print preview event listeners', () => {
|
||||
beforeEach(() => {
|
||||
useCommandStore.setState({ handlers: {} } as any);
|
||||
useAppStore.setState({ modal: { kind: null } } as any);
|
||||
useFileStore.setState({ tree: null, rootPath: null, expanded: new Set(), openTabs: [], activeTabId: null });
|
||||
useFileStore.setState({
|
||||
tree: null,
|
||||
rootPath: null,
|
||||
expanded: new Set(),
|
||||
openTabs: [],
|
||||
activeTabId: null,
|
||||
});
|
||||
useSettingsStore.getState().resetToDefaults?.();
|
||||
useEditorStore.setState({ buffers: new Map(), activeId: null });
|
||||
localStorage.clear();
|
||||
|
||||
@@ -10,8 +10,13 @@ describe('modal commands', () => {
|
||||
localStorage.clear();
|
||||
useCommandStore.setState({ handlers: {}, userBindings: {} } as any);
|
||||
useAppStore.setState({ modal: { kind: null } } as any);
|
||||
useFileStore.setState({ activeTabId: '/x.md', openTabs: [{ id: '/x.md', path: '/x.md', title: 'x.md', dirty: false }] } as any);
|
||||
useEditorStore.setState({ buffers: new Map([['/x.md', { id: '/x.md', path: '/x.md', content: '# hi', dirty: false }]]) } as any);
|
||||
useFileStore.setState({
|
||||
activeTabId: '/x.md',
|
||||
openTabs: [{ id: '/x.md', path: '/x.md', title: 'x.md', dirty: false }],
|
||||
} as any);
|
||||
useEditorStore.setState({
|
||||
buffers: new Map([['/x.md', { id: '/x.md', path: '/x.md', content: '# hi', dirty: false }]]),
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('settings.open opens settings modal', () => {
|
||||
@@ -35,19 +40,34 @@ describe('modal commands', () => {
|
||||
it('file.exportPdf opens export-pdf modal with active path', () => {
|
||||
registerMenuCommands();
|
||||
useCommandStore.getState().dispatch('file.exportPdf');
|
||||
expect(useAppStore.getState().modal).toEqual({ kind: 'export-pdf', props: { sourcePath: '/x.md' } });
|
||||
expect(useAppStore.getState().modal).toEqual({
|
||||
kind: 'export-pdf',
|
||||
props: { sourcePath: '/x.md' },
|
||||
});
|
||||
});
|
||||
|
||||
it('file.exportDocx opens export-docx modal', () => {
|
||||
registerMenuCommands();
|
||||
useCommandStore.getState().dispatch('file.exportDocx');
|
||||
expect(useAppStore.getState().modal).toEqual({ kind: 'export-docx', props: { sourcePath: '/x.md' } });
|
||||
expect(useAppStore.getState().modal).toEqual({
|
||||
kind: 'export-docx',
|
||||
props: { sourcePath: '/x.md' },
|
||||
});
|
||||
});
|
||||
|
||||
it('file.exportBatch opens export-batch modal with all open files', () => {
|
||||
useFileStore.setState({ activeTabId: '/x.md', openTabs: [{ id: '/x.md', path: '/x.md', title: 'x.md', dirty: false }, { id: '/y.md', path: '/y.md', title: 'y.md', dirty: false }] } as any);
|
||||
useFileStore.setState({
|
||||
activeTabId: '/x.md',
|
||||
openTabs: [
|
||||
{ id: '/x.md', path: '/x.md', title: 'x.md', dirty: false },
|
||||
{ id: '/y.md', path: '/y.md', title: 'y.md', dirty: false },
|
||||
],
|
||||
} as any);
|
||||
registerMenuCommands();
|
||||
useCommandStore.getState().dispatch('file.exportBatch');
|
||||
expect(useAppStore.getState().modal).toEqual({ kind: 'export-batch', props: { sourcePaths: ['/x.md', '/y.md'] } });
|
||||
expect(useAppStore.getState().modal).toEqual({
|
||||
kind: 'export-batch',
|
||||
props: { sourcePaths: ['/x.md', '/y.md'] },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,10 @@ describe('Phase 9 commands', () => {
|
||||
localStorage.clear();
|
||||
useCommandStore.setState({ handlers: {}, userBindings: {} } as any);
|
||||
useAppStore.setState({ modal: { kind: null } } as any);
|
||||
useFileStore.setState({ activeTabId: '/x.md', openTabs: [{ id: '/x.md', path: '/x.md', title: 'x.md', dirty: false }] } as any);
|
||||
useFileStore.setState({
|
||||
activeTabId: '/x.md',
|
||||
openTabs: [{ id: '/x.md', path: '/x.md', title: 'x.md', dirty: false }],
|
||||
} as any);
|
||||
// Use resetToDefaults to restore store methods (setSetting/resetToDefaults)
|
||||
useSettingsStore.getState().resetToDefaults();
|
||||
});
|
||||
@@ -36,7 +39,10 @@ describe('Phase 9 commands', () => {
|
||||
it('tools.exportWord opens export-word modal with active path', () => {
|
||||
registerMenuCommands();
|
||||
useCommandStore.getState().dispatch('tools.exportWord');
|
||||
expect(useAppStore.getState().modal).toEqual({ kind: 'export-word', props: { sourcePath: '/x.md' } });
|
||||
expect(useAppStore.getState().modal).toEqual({
|
||||
kind: 'export-word',
|
||||
props: { sourcePath: '/x.md' },
|
||||
});
|
||||
});
|
||||
|
||||
it('tools.repl toggles replOpen setting', () => {
|
||||
@@ -47,4 +53,4 @@ describe('Phase 9 commands', () => {
|
||||
useCommandStore.getState().dispatch('tools.repl');
|
||||
expect(useSettingsStore.getState().replOpen).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,9 @@ describe('useScrollSync', () => {
|
||||
it('throttles editor scroll events to 60fps', () => {
|
||||
const onScroll = vi.fn();
|
||||
const { result } = renderHook(() => useScrollSync({ onEditorScroll: onScroll }));
|
||||
const mockEvt = { currentTarget: { scrollTop: 100, scrollHeight: 1000, clientHeight: 200 } } as any;
|
||||
const mockEvt = {
|
||||
currentTarget: { scrollTop: 100, scrollHeight: 1000, clientHeight: 200 },
|
||||
} as any;
|
||||
// All calls within the same act() - performance.now() stays at 0
|
||||
// First call passes, subsequent calls within FRAME_MS are throttled
|
||||
act(() => {
|
||||
|
||||
@@ -95,7 +95,12 @@ describe('useShortcut', () => {
|
||||
it('calls preventDefault when the shortcut matches', () => {
|
||||
const cb = vi.fn();
|
||||
renderHook(() => useShortcut('mod+s', cb));
|
||||
const event = new KeyboardEvent('keydown', { key: 's', ctrlKey: true, bubbles: true, cancelable: true });
|
||||
const event = new KeyboardEvent('keydown', {
|
||||
key: 's',
|
||||
ctrlKey: true,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
expect(event.defaultPrevented).toBe(true);
|
||||
});
|
||||
@@ -103,7 +108,12 @@ describe('useShortcut', () => {
|
||||
it('does NOT call preventDefault when the shortcut does NOT match', () => {
|
||||
const cb = vi.fn();
|
||||
renderHook(() => useShortcut('mod+s', cb));
|
||||
const event = new KeyboardEvent('keydown', { key: 'x', ctrlKey: true, bubbles: true, cancelable: true });
|
||||
const event = new KeyboardEvent('keydown', {
|
||||
key: 'x',
|
||||
ctrlKey: true,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('toAsciiTable', () => {
|
||||
expect(out).toContain('| X | 100 |');
|
||||
// Right-aligned: "YY" padStart(4) -> " YY", "25" padStart(3) -> " 25"
|
||||
expect(out).toContain('| YY | 25 |');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyAsciiTransform', () => {
|
||||
@@ -57,4 +57,4 @@ describe('applyAsciiTransform', () => {
|
||||
const md = '# Hello\n\nNo tables here.';
|
||||
expect(applyAsciiTransform(md)).toBe(md);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,9 @@ describe('generateDocx', () => {
|
||||
const blob = await generateDocx({ source: '# Hello\n\nWorld' });
|
||||
expect(blob).toBeInstanceOf(Blob);
|
||||
expect(blob.size).toBeGreaterThan(0);
|
||||
expect(blob.type).toBe('application/vnd.openxmlformats-officedocument.wordprocessingml.document');
|
||||
expect(blob.type).toBe(
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
);
|
||||
});
|
||||
|
||||
it('converts headings to docx heading styles', async () => {
|
||||
@@ -21,4 +23,4 @@ describe('generateDocx', () => {
|
||||
expect(blob).toBeInstanceOf(Blob);
|
||||
expect(blob.size).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,4 +38,4 @@ describe('ipc wrapper', () => {
|
||||
expect(result.error.code).toBe('CHANNEL_MISSING');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,4 +27,4 @@ describe('renderMarkdown', () => {
|
||||
const html = renderMarkdown('<script>alert(1)</script>');
|
||||
expect(html).not.toContain('<script>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
fadeIn,
|
||||
slideInRight,
|
||||
modalPop,
|
||||
toastSpring,
|
||||
sidebarToggle,
|
||||
} from '@/lib/motion';
|
||||
import { fadeIn, slideInRight, modalPop, toastSpring, sidebarToggle } from '@/lib/motion';
|
||||
|
||||
describe('motion presets', () => {
|
||||
it('fadeIn is a valid transition object', () => {
|
||||
|
||||
@@ -3,18 +3,19 @@ import { render, act } from '@testing-library/react';
|
||||
import { useCommandStore } from '@/stores/command-store';
|
||||
import { useFileStore } from '@/stores/file-store';
|
||||
import { useAppStore } from '@/stores/app-store';
|
||||
import { useRegisterMenuCommands, useBridgeNativeMenu } from '@/lib/commands/register-menu-commands';
|
||||
import {
|
||||
useRegisterMenuCommands,
|
||||
useBridgeNativeMenu,
|
||||
} from '@/lib/commands/register-menu-commands';
|
||||
|
||||
type Cleanup = () => void;
|
||||
const menuListeners = new Map<string, (...args: unknown[]) => void>();
|
||||
const menuOn = vi.fn(
|
||||
(channel: string, callback: (...args: unknown[]) => void): Cleanup => {
|
||||
menuListeners.set(channel, callback);
|
||||
return () => {
|
||||
if (menuListeners.get(channel) === callback) menuListeners.delete(channel);
|
||||
};
|
||||
}
|
||||
);
|
||||
const menuOn = vi.fn((channel: string, callback: (...args: unknown[]) => void): Cleanup => {
|
||||
menuListeners.set(channel, callback);
|
||||
return () => {
|
||||
if (menuListeners.get(channel) === callback) menuListeners.delete(channel);
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@/lib/ipc', () => ({
|
||||
ipc: {
|
||||
@@ -146,9 +147,7 @@ describe('useRegisterMenuCommands + useBridgeNativeMenu', () => {
|
||||
|
||||
it('file.clearRecent does NOT clear openTabs', () => {
|
||||
useFileStore.setState({
|
||||
openTabs: [
|
||||
{ id: '/a.md', path: '/a.md', title: 'a.md', dirty: false },
|
||||
],
|
||||
openTabs: [{ id: '/a.md', path: '/a.md', title: 'a.md', dirty: false }],
|
||||
activeTabId: '/a.md',
|
||||
});
|
||||
render(<Harness />);
|
||||
|
||||
@@ -21,7 +21,9 @@ describe('settingsSchema', () => {
|
||||
|
||||
describe('exportPdfSchema', () => {
|
||||
it('accepts a4 + normal margins + embedFonts true', () => {
|
||||
expect(exportPdfSchema.safeParse({ format: 'a4', margins: 'normal', embedFonts: true }).success).toBe(true);
|
||||
expect(
|
||||
exportPdfSchema.safeParse({ format: 'a4', margins: 'normal', embedFonts: true }).success
|
||||
).toBe(true);
|
||||
});
|
||||
it('rejects unknown format', () => {
|
||||
expect(exportPdfSchema.safeParse({ format: 'b4' }).success).toBe(false);
|
||||
@@ -39,15 +41,21 @@ describe('exportDocxSchema', () => {
|
||||
|
||||
describe('exportHtmlSchema', () => {
|
||||
it('accepts github highlight style', () => {
|
||||
expect(exportHtmlSchema.safeParse({ standalone: true, highlightStyle: 'github' }).success).toBe(true);
|
||||
expect(exportHtmlSchema.safeParse({ standalone: true, highlightStyle: 'github' }).success).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exportBatchSchema', () => {
|
||||
it('accepts a non-empty file list', () => {
|
||||
expect(exportBatchSchema.safeParse({ format: 'pdf', concurrency: 4, filePaths: ['/a.md'] }).success).toBe(true);
|
||||
expect(
|
||||
exportBatchSchema.safeParse({ format: 'pdf', concurrency: 4, filePaths: ['/a.md'] }).success
|
||||
).toBe(true);
|
||||
});
|
||||
it('rejects empty file list', () => {
|
||||
expect(exportBatchSchema.safeParse({ format: 'pdf', concurrency: 4, filePaths: [] }).success).toBe(false);
|
||||
expect(
|
||||
exportBatchSchema.safeParse({ format: 'pdf', concurrency: 4, filePaths: [] }).success
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,9 +19,14 @@ describe('MigrationRunner', () => {
|
||||
test('runs migration on missing v5 marker, writes v5 file, backs up v4', () => {
|
||||
const dir = tmpDir();
|
||||
fs.writeFileSync(path.join(dir, 'settings.json'), JSON.stringify({ theme: 'light' }));
|
||||
const r = new MigrationRunner({ dir, transform: (v) => ({ ...v, theme: 'system', 'migration.version': 5 }) });
|
||||
const r = new MigrationRunner({
|
||||
dir,
|
||||
transform: (v) => ({ ...v, theme: 'system', 'migration.version': 5 }),
|
||||
});
|
||||
expect(r.run()).toBe('migrated');
|
||||
expect(JSON.parse(fs.readFileSync(path.join(dir, 'settings.json'), 'utf-8'))['migration.version']).toBe(5);
|
||||
expect(
|
||||
JSON.parse(fs.readFileSync(path.join(dir, 'settings.json'), 'utf-8'))['migration.version']
|
||||
).toBe(5);
|
||||
expect(fs.existsSync(path.join(dir, 'settings.v4.bak.json'))).toBe(true);
|
||||
fs.rmSync(dir, { recursive: true });
|
||||
});
|
||||
@@ -37,16 +42,28 @@ describe('MigrationRunner', () => {
|
||||
test('returns failed and writes v5 marker so subsequent runs skip migration', () => {
|
||||
const dir = tmpDir();
|
||||
fs.writeFileSync(path.join(dir, 'settings.json'), JSON.stringify({ theme: 'light' }));
|
||||
const r = new MigrationRunner({ dir, transform: () => { throw new Error('boom'); } });
|
||||
const r = new MigrationRunner({
|
||||
dir,
|
||||
transform: () => {
|
||||
throw new Error('boom');
|
||||
},
|
||||
});
|
||||
expect(r.run()).toBe('failed');
|
||||
// v5 marker is written so next launch skips migration
|
||||
expect(JSON.parse(fs.readFileSync(path.join(dir, 'settings.json'), 'utf-8'))['migration.version']).toBe(5);
|
||||
expect(
|
||||
JSON.parse(fs.readFileSync(path.join(dir, 'settings.json'), 'utf-8'))['migration.version']
|
||||
).toBe(5);
|
||||
// Original data is preserved (theme: light is still there)
|
||||
expect(fs.readFileSync(path.join(dir, 'settings.json'), 'utf-8')).toContain('light');
|
||||
// Backup exists
|
||||
expect(fs.existsSync(path.join(dir, 'settings.v4.bak.json'))).toBe(true);
|
||||
// Subsequent run skips
|
||||
const r2 = new MigrationRunner({ dir, transform: () => { throw new Error('should not be called'); } });
|
||||
const r2 = new MigrationRunner({
|
||||
dir,
|
||||
transform: () => {
|
||||
throw new Error('should not be called');
|
||||
},
|
||||
});
|
||||
expect(r2.run()).toBe('skipped');
|
||||
fs.rmSync(dir, { recursive: true });
|
||||
});
|
||||
|
||||
@@ -30,10 +30,7 @@ describe('UpdaterService', () => {
|
||||
emitter.emit('update-available', { version: '5.0.2' });
|
||||
});
|
||||
await service.check();
|
||||
expect(states).toEqual([
|
||||
{ state: 'checking' },
|
||||
{ state: 'available', version: '5.0.2' },
|
||||
]);
|
||||
expect(states).toEqual([{ state: 'checking' }, { state: 'available', version: '5.0.2' }]);
|
||||
});
|
||||
|
||||
test('check() emits error on network failure', async () => {
|
||||
@@ -57,4 +54,4 @@ describe('UpdaterService', () => {
|
||||
service.install();
|
||||
expect(mockAutoUpdater.quitAndInstall).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,7 +53,10 @@ describe('useAppStore (modal)', () => {
|
||||
|
||||
it('openModal with kind requiring props passes them through', () => {
|
||||
useAppStore.getState().openModal('export-pdf', { sourcePath: '/a.md' });
|
||||
expect(useAppStore.getState().modal).toEqual({ kind: 'export-pdf', props: { sourcePath: '/a.md' } });
|
||||
expect(useAppStore.getState().modal).toEqual({
|
||||
kind: 'export-pdf',
|
||||
props: { sourcePath: '/a.md' },
|
||||
});
|
||||
});
|
||||
|
||||
it('closeModal clears the modal state', () => {
|
||||
|
||||
@@ -44,4 +44,4 @@ describe('useEditorStore', () => {
|
||||
expect(newBuf?.path).toBe('/bar.md');
|
||||
expect(useEditorStore.getState().activeId).toBe('file-2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,7 +54,9 @@ describe('file-store toasts', () => {
|
||||
openTabs: [{ id: '/test.md', path: '/test.md', title: 'test.md', dirty: false }],
|
||||
} as any);
|
||||
useEditorStore.setState({
|
||||
buffers: new Map([['/test.md', { id: '/test.md', path: '/test.md', content: '# hi', dirty: true }]]),
|
||||
buffers: new Map([
|
||||
['/test.md', { id: '/test.md', path: '/test.md', content: '# hi', dirty: true }],
|
||||
]),
|
||||
activeId: '/test.md',
|
||||
} as any);
|
||||
|
||||
@@ -64,13 +66,18 @@ describe('file-store toasts', () => {
|
||||
});
|
||||
|
||||
it('calls toast.error when save fails', async () => {
|
||||
fakeWrite.mockResolvedValue({ ok: false, error: { code: 'EACCES', message: 'Permission denied' } });
|
||||
fakeWrite.mockResolvedValue({
|
||||
ok: false,
|
||||
error: { code: 'EACCES', message: 'Permission denied' },
|
||||
});
|
||||
useFileStore.setState({
|
||||
activeTabId: '/test.md',
|
||||
openTabs: [{ id: '/test.md', path: '/test.md', title: 'test.md', dirty: false }],
|
||||
} as any);
|
||||
useEditorStore.setState({
|
||||
buffers: new Map([['/test.md', { id: '/test.md', path: '/test.md', content: '# hi', dirty: true }]]),
|
||||
buffers: new Map([
|
||||
['/test.md', { id: '/test.md', path: '/test.md', content: '# hi', dirty: true }],
|
||||
]),
|
||||
activeId: '/test.md',
|
||||
} as any);
|
||||
|
||||
@@ -91,10 +98,13 @@ describe('file-store toasts', () => {
|
||||
|
||||
describe('openFolder', () => {
|
||||
it('calls toast.error when IPC list fails', async () => {
|
||||
fakeList.mockResolvedValue({ ok: false, error: { code: 'EACCES', message: 'Permission denied' } });
|
||||
fakeList.mockResolvedValue({
|
||||
ok: false,
|
||||
error: { code: 'EACCES', message: 'Permission denied' },
|
||||
});
|
||||
|
||||
await useFileStore.getState().openFolder('/forbidden');
|
||||
expect(toast.error).toHaveBeenCalledWith('Failed to open folder: Permission denied');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,10 +49,7 @@ describe('useFileStore', () => {
|
||||
it('openFolder calls ipc.file.list and sets tree with root node and mapped children', async () => {
|
||||
fakeList.mockResolvedValue({
|
||||
ok: true,
|
||||
data: [
|
||||
fakeFileEntry('README.md', false),
|
||||
fakeFileEntry('src', true),
|
||||
],
|
||||
data: [fakeFileEntry('README.md', false), fakeFileEntry('src', true)],
|
||||
});
|
||||
|
||||
await useFileStore.getState().openFolder('/root');
|
||||
@@ -139,7 +136,9 @@ describe('useFileStore', () => {
|
||||
path: '/root',
|
||||
isDirectory: true,
|
||||
loaded: true,
|
||||
children: [{ name: 'src', path: '/root/src', isDirectory: true, children: null, loaded: true }],
|
||||
children: [
|
||||
{ name: 'src', path: '/root/src', isDirectory: true, children: null, loaded: true },
|
||||
],
|
||||
};
|
||||
useFileStore.setState({ tree });
|
||||
|
||||
|
||||
@@ -41,4 +41,4 @@ describe('useSettingsStore', () => {
|
||||
expect(parsed.state.setSetting).toBeUndefined();
|
||||
expect(parsed.state.resetToDefaults).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user