Files
markdown-converter/tests/document-compare-dialog.test.js
T

182 lines
6.8 KiB
JavaScript

/**
* Tests for the Document Compare dialog (local two-file diff and git-HEAD diff).
* Exercises the real dialog DOM in jsdom with the electron IPC surface mocked,
* following the jest.mock('electron') pattern in monospace-font-config.test.js.
*/
jest.mock('electron', () => ({
ipcRenderer: {
invoke: jest.fn(),
send: jest.fn(),
on: jest.fn(),
once: jest.fn(),
removeAllListeners: jest.fn(),
},
}));
require('../src/utils/ModalManager'); // sets window.ModalManager for the dialog
const { ipcRenderer } = require('electron');
const { showDocumentCompareDialog } = require('../src/renderer/document-compare-dialog');
const flush = () => new Promise((resolve) => setTimeout(resolve, 20));
function openDialog(filePath = null) {
showDocumentCompareDialog({ filePath });
}
function clickCompare() {
document.getElementById('document-compare-run').click();
}
function resultRows() {
return Array.from(document.querySelectorAll('#compare-result .diff-row'));
}
function statusText() {
return document.getElementById('compare-status-message').textContent;
}
describe('Document Compare dialog', () => {
beforeEach(() => {
ipcRenderer.invoke.mockReset();
openDialog('/notes/report.md');
});
describe('local two-file mode', () => {
it('renders added, removed, and unchanged rows from the line diff', async () => {
ipcRenderer.invoke.mockImplementation((channel, filePath) => {
if (channel !== 'read-file') return {};
if (filePath === '/notes/old.md') return '# Title\nold line\nshared tail';
if (filePath === '/notes/new.md') return '# Title\nnew line\nshared tail';
throw new Error(`unexpected path ${filePath}`);
});
document.getElementById('compare-file-a-input').value = '/notes/old.md';
document.getElementById('compare-file-b-input').value = '/notes/new.md';
clickCompare();
await flush();
const rows = resultRows();
expect(rows).toHaveLength(4);
expect(rows[0].className).toBe('diff-row diff-context');
expect(rows[1].className).toBe('diff-row diff-removed');
expect(rows[1].textContent).toContain('old line');
expect(rows[2].className).toBe('diff-row diff-added');
expect(rows[2].textContent).toContain('new line');
expect(rows[3].className).toBe('diff-row diff-context');
expect(statusText()).toBe('1 line(s) added, 1 line(s) removed.');
});
it('reports identical files without rendering a diff view', async () => {
ipcRenderer.invoke.mockImplementation(() => 'same\ncontent');
document.getElementById('compare-file-a-input').value = '/a.md';
document.getElementById('compare-file-b-input').value = '/b.md';
clickCompare();
await flush();
expect(statusText()).toBe('Files are identical.');
expect(document.getElementById('compare-result').classList.contains('hidden')).toBe(true);
});
it('warns when one of the two files has not been chosen', async () => {
document.getElementById('compare-file-a-input').value = '/a.md';
document.getElementById('compare-file-b-input').value = '';
clickCompare();
await flush();
expect(statusText()).toBe('Choose both files to compare.');
expect(ipcRenderer.invoke).not.toHaveBeenCalledWith('read-file', expect.anything());
});
it('shows a warning when a file cannot be read', async () => {
ipcRenderer.invoke.mockRejectedValue(new Error('Invalid file path'));
document.getElementById('compare-file-a-input').value = '/a.md';
document.getElementById('compare-file-b-input').value = '/b.md';
clickCompare();
await flush();
expect(statusText()).toContain('Error reading file');
});
});
describe('git HEAD mode', () => {
function selectGitMode() {
const modeSelect = document.getElementById('compare-mode-select');
modeSelect.value = 'git';
modeSelect.dispatchEvent(new Event('change'));
}
it('renders git raw diff text verbatim, colored by leading character', async () => {
ipcRenderer.invoke.mockImplementation((channel) => {
if (channel === 'git-status') return { current: 'master', files: [] };
if (channel === 'git-diff') {
return 'diff --git a/report.md b/report.md\n@@ -1,2 +1,2 @@\n context\n-old line\n+new line';
}
return {};
});
await flush(); // git-status availability check
selectGitMode();
clickCompare();
await flush();
const rows = resultRows();
expect(rows).toHaveLength(5);
expect(rows[0].className).toBe('diff-row diff-context');
expect(rows[1].className).toBe('diff-row diff-hunk');
expect(rows[2].className).toBe('diff-row diff-context');
expect(rows[3].className).toBe('diff-row diff-removed');
expect(rows[3].textContent).toBe('-old line');
expect(rows[4].className).toBe('diff-row diff-added');
expect(rows[4].textContent).toBe('+new line');
expect(ipcRenderer.invoke).toHaveBeenCalledWith('git-diff', {
file: '/notes/report.md',
againstHead: true,
});
});
it('reports no differences when git returns an empty diff', async () => {
ipcRenderer.invoke.mockImplementation((channel) => {
if (channel === 'git-status') return { current: 'master', files: [] };
if (channel === 'git-diff') return '';
return {};
});
await flush();
selectGitMode();
clickCompare();
await flush();
expect(statusText()).toBe('No differences against HEAD.');
expect(document.getElementById('compare-result').classList.contains('hidden')).toBe(true);
});
it('degrades to a disabled option with a hint outside a git repository', async () => {
ipcRenderer.invoke.mockImplementation((channel) => {
if (channel === 'git-status') return { error: 'Not a git repository' };
return {};
});
openDialog('/plain/file.md');
await flush();
const gitOption = document.querySelector('#compare-mode-select option[value="git"]');
expect(gitOption.disabled).toBe(true);
const hint = document.getElementById('compare-git-hint');
expect(hint.classList.contains('hidden')).toBe(false);
expect(hint.textContent).toBe('Current file is not inside a git repository.');
expect(ipcRenderer.invoke).not.toHaveBeenCalledWith('git-diff', expect.anything());
});
it('degrades to a disabled option with a hint when no file is open', async () => {
openDialog(null);
const gitOption = document.querySelector('#compare-mode-select option[value="git"]');
expect(gitOption.disabled).toBe(true);
const hint = document.getElementById('compare-git-hint');
expect(hint.textContent).toContain('Open (or save) a file');
expect(ipcRenderer.invoke).not.toHaveBeenCalledWith('git-diff', expect.anything());
});
});
});