setup i18n and ported all tools to standalone pages
This commit is contained in:
@@ -1,371 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { addBlankPage } from '@/js/logic/add-blank-page';
|
||||
import * as ui from '@/js/ui';
|
||||
import * as helpers from '@/js/utils/helpers';
|
||||
import { state } from '@/js/state';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
|
||||
// -------------------- Mock Modules --------------------
|
||||
vi.mock('@/js/ui', () => ({
|
||||
showLoader: vi.fn(),
|
||||
hideLoader: vi.fn(),
|
||||
showAlert: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/js/utils/helpers', () => ({
|
||||
downloadFile: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('pdf-lib', () => ({
|
||||
PDFDocument: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// -------------------- Test Suite --------------------
|
||||
describe('Add Blank Page Tool', () => {
|
||||
let mockNewDoc: any;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset state pdfDoc
|
||||
state.pdfDoc = {
|
||||
getPageCount: () => 5,
|
||||
getPage: vi.fn((index: number) => ({
|
||||
getSize: () => ({ width: 595.28, height: 841.89 }), // A4 size
|
||||
})),
|
||||
} as any;
|
||||
|
||||
// Mock PDFDocument.create
|
||||
mockNewDoc = {
|
||||
copyPages: vi.fn((doc: any, indices: number[]) =>
|
||||
Promise.resolve(indices.map((i: number) => ({ page: `page-${i}` })))
|
||||
),
|
||||
addPage: vi.fn(),
|
||||
save: vi.fn(() => Promise.resolve(new Uint8Array([1, 2, 3]))),
|
||||
};
|
||||
vi.mocked(PDFLibDocument.create).mockResolvedValue(mockNewDoc);
|
||||
|
||||
// Mock helpers and UI
|
||||
vi.mocked(helpers.downloadFile).mockImplementation(() => {});
|
||||
vi.mocked(ui.showLoader).mockImplementation(() => {});
|
||||
vi.mocked(ui.hideLoader).mockImplementation(() => {});
|
||||
vi.mocked(ui.showAlert).mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
// -------------------- Input Validation Tests --------------------
|
||||
describe('Input Validation', () => {
|
||||
it('should show alert for empty page number', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Invalid Input',
|
||||
'Please enter a page number.'
|
||||
);
|
||||
expect(ui.showLoader).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show alert for empty page count', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="2" />
|
||||
<input id="page-count" value="" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Invalid Input',
|
||||
'Please enter the number of pages to insert.'
|
||||
);
|
||||
expect(ui.showLoader).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show alert for invalid page number (negative)', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="-1" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Invalid Input',
|
||||
'Please enter a number between 0 and 5.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should show alert for invalid page number (too high)', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="10" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Invalid Input',
|
||||
'Please enter a number between 0 and 5.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should show alert for invalid page count (zero)', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="2" />
|
||||
<input id="page-count" value="0" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Invalid Input',
|
||||
'Please enter a valid number of pages (1 or more).'
|
||||
);
|
||||
});
|
||||
|
||||
it('should show alert for invalid page count (negative)', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="2" />
|
||||
<input id="page-count" value="-5" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Invalid Input',
|
||||
'Please enter a valid number of pages (1 or more).'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------- Single Page Insertion Tests --------------------
|
||||
describe('Single Page Insertion', () => {
|
||||
it('should add one blank page at the beginning', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="0" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalledWith('Adding 1 blank page...');
|
||||
expect(PDFLibDocument.create).toHaveBeenCalled();
|
||||
// Should add 1 blank page + 5 existing pages = 6 total calls
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(6);
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalledWith([595.28, 841.89]);
|
||||
expect(mockNewDoc.save).toHaveBeenCalled();
|
||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||
expect.any(Blob),
|
||||
'blank-page-added.pdf'
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add one blank page in the middle', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="2" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalledWith('Adding 1 blank page...');
|
||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [0, 1]);
|
||||
// Should add 1 blank page + 5 existing pages = 6 total calls
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(6);
|
||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(
|
||||
state.pdfDoc,
|
||||
[2, 3, 4]
|
||||
);
|
||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||
expect.any(Blob),
|
||||
'blank-page-added.pdf'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add one blank page at the end', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="5" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(
|
||||
state.pdfDoc,
|
||||
[0, 1, 2, 3, 4]
|
||||
);
|
||||
// Should add 1 blank page + 5 existing pages = 6 total calls
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(6);
|
||||
// When adding at the end, there are no pages after, so copyPages is not called for indicesAfter
|
||||
expect(mockNewDoc.copyPages).toHaveBeenCalledTimes(1);
|
||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||
expect.any(Blob),
|
||||
'blank-page-added.pdf'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------- Multiple Pages Insertion Tests --------------------
|
||||
describe('Multiple Pages Insertion', () => {
|
||||
it('should add multiple blank pages at the beginning', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="0" />
|
||||
<input id="page-count" value="3" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalledWith('Adding 3 blank pages...');
|
||||
// Should add 3 blank pages + 5 existing pages = 8 total calls
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(8);
|
||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||
expect.any(Blob),
|
||||
'blank-pages-added.pdf'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add multiple blank pages in the middle', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="2" />
|
||||
<input id="page-count" value="5" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalledWith('Adding 5 blank pages...');
|
||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(state.pdfDoc, [0, 1]);
|
||||
// Should add 5 blank pages + 5 existing pages = 10 total calls
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(10);
|
||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(
|
||||
state.pdfDoc,
|
||||
[2, 3, 4]
|
||||
);
|
||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||
expect.any(Blob),
|
||||
'blank-pages-added.pdf'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add multiple blank pages at the end', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="5" />
|
||||
<input id="page-count" value="2" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalledWith('Adding 2 blank pages...');
|
||||
expect(mockNewDoc.copyPages).toHaveBeenCalledWith(
|
||||
state.pdfDoc,
|
||||
[0, 1, 2, 3, 4]
|
||||
);
|
||||
// Should add 2 blank pages + 5 existing pages = 7 total calls
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(7);
|
||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||
expect.any(Blob),
|
||||
'blank-pages-added.pdf'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------- Error Handling Tests --------------------
|
||||
describe('Error Handling', () => {
|
||||
it('should handle PDF creation errors', async () => {
|
||||
vi.mocked(PDFLibDocument.create).mockRejectedValue(
|
||||
new Error('PDF creation failed')
|
||||
);
|
||||
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="2" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Error',
|
||||
'Could not add blank page.'
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle PDF processing errors', async () => {
|
||||
mockNewDoc.copyPages.mockRejectedValue(new Error('Copy failed'));
|
||||
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="2" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Error',
|
||||
'Could not add blank page.'
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle save errors', async () => {
|
||||
mockNewDoc.save.mockRejectedValue(new Error('Save failed'));
|
||||
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="2" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Error',
|
||||
'Could not add blank page.'
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------- Edge Cases Tests --------------------
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty PDF (0 pages)', async () => {
|
||||
state.pdfDoc.getPageCount = () => 0;
|
||||
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="0" />
|
||||
<input id="page-count" value="1" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
// When PDF has 0 pages, copyPages is not called at all
|
||||
expect(mockNewDoc.copyPages).not.toHaveBeenCalled();
|
||||
// Should add 1 blank page + 0 existing pages = 1 total call
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle large number of pages', async () => {
|
||||
document.body.innerHTML = `
|
||||
<input id="page-number" value="2" />
|
||||
<input id="page-count" value="100" />
|
||||
`;
|
||||
|
||||
await addBlankPage();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalledWith('Adding 100 blank pages...');
|
||||
// Should add 100 blank pages + 5 existing pages = 105 total calls
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalledTimes(105);
|
||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||
expect.any(Blob),
|
||||
'blank-pages-added.pdf'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,170 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { state } from '@/js/state';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import Sortable from 'sortablejs';
|
||||
import * as helpers from '@/js/utils/helpers';
|
||||
import * as ui from '@/js/ui';
|
||||
import {
|
||||
setupAlternateMergeTool,
|
||||
alternateMerge,
|
||||
} from '@/js/logic/alternate-merge';
|
||||
|
||||
vi.mock('pdf-lib', () => ({
|
||||
PDFDocument: {
|
||||
create: vi.fn(),
|
||||
load: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('sortablejs', () => ({
|
||||
default: { create: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock('@/js/utils/helpers', () => ({
|
||||
readFileAsArrayBuffer: vi.fn(),
|
||||
downloadFile: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/js/ui', () => ({
|
||||
showLoader: vi.fn(),
|
||||
hideLoader: vi.fn(),
|
||||
showAlert: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('Alternate Merge Tool', () => {
|
||||
let mockPdfDoc1: any;
|
||||
let mockPdfDoc2: any;
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<div id="alternate-merge-options" class="hidden"></div>
|
||||
<button id="process-btn"></button>
|
||||
<ul id="alternate-file-list"></ul>
|
||||
`;
|
||||
|
||||
state.files = [
|
||||
new File(['dummy1'], 'file1.pdf'),
|
||||
new File(['dummy2'], 'file2.pdf'),
|
||||
];
|
||||
|
||||
mockPdfDoc1 = { getPageCount: vi.fn(() => 2) };
|
||||
mockPdfDoc2 = { getPageCount: vi.fn(() => 3) };
|
||||
|
||||
vi.mocked(helpers.readFileAsArrayBuffer).mockResolvedValue(
|
||||
new ArrayBuffer(8)
|
||||
);
|
||||
vi.mocked(PDFDocument.load)
|
||||
.mockResolvedValueOnce(mockPdfDoc1)
|
||||
.mockResolvedValueOnce(mockPdfDoc2);
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('setupAlternateMergeTool()', () => {
|
||||
it('should initialize UI and load PDF files', async () => {
|
||||
await setupAlternateMergeTool();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalledWith('Loading PDF documents...');
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
expect(PDFDocument.load).toHaveBeenCalledTimes(2);
|
||||
expect(document.querySelectorAll('#alternate-file-list li').length).toBe(
|
||||
2
|
||||
);
|
||||
expect(Sortable.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show alert on load failure', async () => {
|
||||
vi.mocked(PDFDocument.load).mockReset();
|
||||
vi.mocked(PDFDocument.load).mockRejectedValueOnce(new Error('bad pdf'));
|
||||
|
||||
await setupAlternateMergeTool();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Error',
|
||||
expect.stringContaining('Failed to load one or more PDF files')
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('alternateMerge()', () => {
|
||||
it('should show alert if less than 2 PDFs loaded', async () => {
|
||||
// Setup with only 1 file - need to call setup first to populate internal state
|
||||
state.files = [new File(['dummy1'], 'file1.pdf')];
|
||||
vi.mocked(PDFDocument.load).mockReset();
|
||||
vi.mocked(PDFDocument.load).mockResolvedValueOnce(mockPdfDoc1);
|
||||
|
||||
await setupAlternateMergeTool();
|
||||
vi.clearAllMocks(); // Clear the setup calls
|
||||
|
||||
await alternateMerge();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Not Enough Files',
|
||||
expect.stringContaining('Please upload at least two PDF files')
|
||||
);
|
||||
});
|
||||
|
||||
it('should merge pages alternately and download file', async () => {
|
||||
// First setup the tool to populate internal state
|
||||
await setupAlternateMergeTool();
|
||||
vi.clearAllMocks(); // Clear setup calls
|
||||
|
||||
const mockCopyPages = vi.fn(() =>
|
||||
Promise.resolve([{ page: 'mockPage' }] as any)
|
||||
);
|
||||
const mockAddPage = vi.fn();
|
||||
const mockSave = vi.fn(() => Promise.resolve(new Uint8Array([1, 2, 3])));
|
||||
|
||||
vi.mocked(PDFDocument.create).mockResolvedValue({
|
||||
copyPages: mockCopyPages,
|
||||
addPage: mockAddPage,
|
||||
save: mockSave,
|
||||
} as any);
|
||||
|
||||
const fileList = document.getElementById('alternate-file-list')!;
|
||||
// The list should already be populated by setupAlternateMergeTool
|
||||
// But ensure it has the correct structure
|
||||
fileList.innerHTML = `
|
||||
<li data-file-name="file1.pdf"></li>
|
||||
<li data-file-name="file2.pdf"></li>
|
||||
`;
|
||||
|
||||
await alternateMerge();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Alternating')
|
||||
);
|
||||
expect(mockCopyPages).toHaveBeenCalled();
|
||||
expect(mockAddPage).toHaveBeenCalled();
|
||||
expect(mockSave).toHaveBeenCalled();
|
||||
expect(helpers.downloadFile).toHaveBeenCalled();
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Success',
|
||||
expect.stringContaining('mixed successfully')
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show alert on merge error', async () => {
|
||||
// Setup the tool first to populate internal state with 2 PDFs
|
||||
await setupAlternateMergeTool();
|
||||
vi.clearAllMocks(); // Clear setup calls
|
||||
|
||||
// Mock PDFDocument.create to reject
|
||||
vi.mocked(PDFDocument.create).mockRejectedValue(new Error('broken'));
|
||||
|
||||
await alternateMerge();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Error',
|
||||
expect.stringContaining('An error occurred while mixing')
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,204 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
setupRemoveBlankPagesTool,
|
||||
removeBlankPages,
|
||||
} from '@/js/logic/remove-blank-pages';
|
||||
import * as ui from '@/js/ui';
|
||||
import * as helpers from '@/js/utils/helpers';
|
||||
import { state } from '@/js/state';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
|
||||
if (typeof ImageData === 'undefined') {
|
||||
global.ImageData = class {
|
||||
data: Uint8ClampedArray;
|
||||
width: number;
|
||||
height: number;
|
||||
colorSpace: string;
|
||||
constructor(
|
||||
data: Uint8ClampedArray,
|
||||
width: number,
|
||||
height: number,
|
||||
options?: { colorSpace: string }
|
||||
) {
|
||||
this.data = data;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colorSpace = options?.colorSpace || 'srgb';
|
||||
}
|
||||
} as any;
|
||||
}
|
||||
|
||||
const mockContext: CanvasRenderingContext2D = {
|
||||
getImageData: vi.fn(),
|
||||
drawImage: vi.fn(),
|
||||
putImageData: vi.fn(),
|
||||
} as unknown as CanvasRenderingContext2D;
|
||||
|
||||
HTMLCanvasElement.prototype.getContext = vi.fn().mockReturnValue(mockContext);
|
||||
HTMLCanvasElement.prototype.toDataURL = vi
|
||||
.fn()
|
||||
.mockReturnValue('data:image/png;base64,mock');
|
||||
|
||||
function createMockPage(isBlank: boolean) {
|
||||
return {
|
||||
getViewport: vi.fn(({ scale }) => ({
|
||||
width: 800 * scale,
|
||||
height: 600 * scale,
|
||||
scale,
|
||||
})),
|
||||
render: vi.fn(() => {
|
||||
// Return ImageData depending on blank/content
|
||||
mockContext.getImageData = vi.fn(() => {
|
||||
const size = 100 * 100 * 4;
|
||||
const data = new Uint8ClampedArray(size);
|
||||
|
||||
if (isBlank) {
|
||||
data.fill(255); // fully white = blank
|
||||
} else {
|
||||
// Mostly black pixels to simulate content
|
||||
data.fill(0);
|
||||
// leave some white pixels so analysis updates text
|
||||
for (let i = 0; i < 400; i += 4) {
|
||||
data[i] = 255;
|
||||
data[i + 1] = 255;
|
||||
data[i + 2] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
return new ImageData(data, 100, 100, { colorSpace: 'srgb' });
|
||||
});
|
||||
return { promise: Promise.resolve() };
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------- MOCK pdfjs-dist --------------------
|
||||
const mockPdfJsDoc = {
|
||||
numPages: 3,
|
||||
getPage: vi.fn((pageNum: number) => {
|
||||
if (pageNum === 2) return Promise.resolve(createMockPage(true)); // Page 2 blank
|
||||
return Promise.resolve(createMockPage(false)); // Pages 1 & 3 content
|
||||
}),
|
||||
};
|
||||
|
||||
vi.mock('pdfjs-dist', () => ({
|
||||
getDocument: vi.fn(() => ({ promise: Promise.resolve(mockPdfJsDoc) })),
|
||||
}));
|
||||
|
||||
// -------------------- MOCK MODULES --------------------
|
||||
vi.mock('@/js/ui', () => ({
|
||||
showLoader: vi.fn(),
|
||||
hideLoader: vi.fn(),
|
||||
showAlert: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/js/utils/helpers', () => ({
|
||||
downloadFile: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('pdf-lib', () => ({
|
||||
PDFDocument: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// -------------------- TEST SUITE --------------------
|
||||
describe('Remove Blank Pages Tool', () => {
|
||||
let mockNewDoc: any;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset state pdfDoc
|
||||
state.pdfDoc = {
|
||||
getPageCount: () => 3,
|
||||
save: vi.fn(),
|
||||
copyPages: vi.fn(),
|
||||
addPage: vi.fn(),
|
||||
} as any;
|
||||
|
||||
// Mock PDFDocument.create
|
||||
mockNewDoc = {
|
||||
copyPages: vi.fn((doc: any, indices: number[]) =>
|
||||
Promise.resolve(indices.map((i: number) => ({ page: `page-${i}` })))
|
||||
),
|
||||
addPage: vi.fn(),
|
||||
save: vi.fn(() => Promise.resolve(new Uint8Array([4, 5, 6]))),
|
||||
};
|
||||
vi.mocked(PDFDocument.create).mockResolvedValue(mockNewDoc);
|
||||
|
||||
// Mock helpers and UI
|
||||
vi.mocked(helpers.downloadFile).mockImplementation(() => {});
|
||||
vi.mocked(ui.showLoader).mockImplementation(() => {});
|
||||
vi.mocked(ui.hideLoader).mockImplementation(() => {});
|
||||
vi.mocked(ui.showAlert).mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
// -------------------- TEST CASES --------------------
|
||||
|
||||
it('should remove blank pages successfully', async () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="analysis-text"></div>
|
||||
<input id="sensitivity-slider" type="range" min="0" max="100" value="95" />
|
||||
<span id="sensitivity-value"></span>
|
||||
<div id="analysis-preview" class="hidden"></div>
|
||||
<div id="removed-pages-thumbnails"></div>
|
||||
`;
|
||||
|
||||
await setupRemoveBlankPagesTool();
|
||||
await removeBlankPages();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalledWith('Removing blank pages...');
|
||||
expect(PDFDocument.create).toHaveBeenCalled();
|
||||
expect(mockNewDoc.copyPages).toHaveBeenCalled();
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalled();
|
||||
expect(mockNewDoc.save).toHaveBeenCalled();
|
||||
expect(helpers.downloadFile).toHaveBeenCalled();
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle all pages blank gracefully', async () => {
|
||||
mockPdfJsDoc.getPage = vi.fn(() => Promise.resolve(createMockPage(true))); // all pages blank
|
||||
|
||||
document.body.innerHTML = `
|
||||
<div id="analysis-text"></div>
|
||||
<input id="sensitivity-slider" type="range" min="0" max="100" value="95" />
|
||||
<span id="sensitivity-value"></span>
|
||||
<div id="analysis-preview" class="hidden"></div>
|
||||
<div id="removed-pages-thumbnails"></div>
|
||||
`;
|
||||
|
||||
await setupRemoveBlankPagesTool();
|
||||
await removeBlankPages();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'No Content Found',
|
||||
expect.stringContaining('All pages were identified as blank')
|
||||
);
|
||||
expect(helpers.downloadFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle no blank pages gracefully', async () => {
|
||||
mockPdfJsDoc.getPage = vi.fn(() => Promise.resolve(createMockPage(false))); // all pages content
|
||||
|
||||
document.body.innerHTML = `
|
||||
<div id="analysis-text"></div>
|
||||
<input id="sensitivity-slider" type="range" min="0" max="100" value="95" />
|
||||
<span id="sensitivity-value"></span>
|
||||
<div id="analysis-preview" class="hidden"></div>
|
||||
<div id="removed-pages-thumbnails"></div>
|
||||
`;
|
||||
|
||||
await setupRemoveBlankPagesTool();
|
||||
await removeBlankPages();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'No Pages Removed',
|
||||
'No pages were identified as blank at the current sensitivity level.'
|
||||
);
|
||||
expect(helpers.downloadFile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,140 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { reversePages } from '../js/logic/reverse-pages';
|
||||
import { state } from '../js/state';
|
||||
import * as helpers from '../js/utils/helpers';
|
||||
import * as ui from '../js/ui';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
vi.mock('../js/ui', () => ({
|
||||
showLoader: vi.fn(),
|
||||
hideLoader: vi.fn(),
|
||||
showAlert: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../js/utils/helpers', () => ({
|
||||
downloadFile: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('pdf-lib', () => ({
|
||||
PDFDocument: {
|
||||
create: vi.fn(),
|
||||
load: vi.fn().mockResolvedValue({
|
||||
getPageCount: vi.fn(() => 2),
|
||||
copyPages: vi.fn((_, indices) =>
|
||||
Promise.resolve(indices.map((i) => ({ page: `page-${i}` })))
|
||||
),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('reversePages - multi PDF support', () => {
|
||||
let mockNewDoc: any;
|
||||
|
||||
beforeEach(() => {
|
||||
state.files = []; // ✅ now using files, not pdfDocs
|
||||
|
||||
mockNewDoc = {
|
||||
copyPages: vi.fn((doc: any, indices: number[]) =>
|
||||
Promise.resolve(indices.map((i: number) => ({ page: `page-${i}` })))
|
||||
),
|
||||
addPage: vi.fn(),
|
||||
save: vi.fn(() => Promise.resolve(new Uint8Array([1, 2, 3]))),
|
||||
};
|
||||
vi.mocked(PDFLibDocument.create).mockResolvedValue(mockNewDoc);
|
||||
|
||||
vi.mocked(helpers.downloadFile).mockImplementation(() => {});
|
||||
vi.mocked(ui.showLoader).mockImplementation(() => {});
|
||||
vi.mocked(ui.hideLoader).mockImplementation(() => {});
|
||||
vi.mocked(ui.showAlert).mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should reverse pages for multiple PDFs and create a zip', async () => {
|
||||
const mockFile = (name: string) => ({
|
||||
name,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(8)),
|
||||
});
|
||||
|
||||
state.files = [mockFile('a.pdf'), mockFile('b.pdf')]; // ✅ now matches function
|
||||
|
||||
await reversePages();
|
||||
|
||||
expect(helpers.downloadFile).toHaveBeenCalledWith(
|
||||
expect.any(Blob),
|
||||
'reversed_pdfs.zip'
|
||||
);
|
||||
expect(mockNewDoc.copyPages).toHaveBeenCalledTimes(2);
|
||||
expect(mockNewDoc.addPage).toHaveBeenCalled();
|
||||
expect(mockNewDoc.save).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should handle empty PDF list gracefully', async () => {
|
||||
state.files = [];
|
||||
await reversePages();
|
||||
expect(ui.showAlert).toHaveBeenCalledWith('Error', 'PDF not loaded.');
|
||||
});
|
||||
|
||||
it('should handle PDF creation errors', async () => {
|
||||
vi.mocked(PDFLibDocument.create).mockRejectedValue(
|
||||
new Error('Create failed')
|
||||
);
|
||||
state.files = [
|
||||
{
|
||||
name: 'x.pdf',
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(8)),
|
||||
},
|
||||
];
|
||||
|
||||
await reversePages();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Error',
|
||||
'Could not reverse the PDF pages.'
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle PDF processing errors', async () => {
|
||||
mockNewDoc.copyPages.mockRejectedValue(new Error('Copy failed'));
|
||||
state.files = [
|
||||
{
|
||||
name: 'y.pdf',
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(8)),
|
||||
},
|
||||
];
|
||||
|
||||
await reversePages();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Error',
|
||||
'Could not reverse the PDF pages.'
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle save errors', async () => {
|
||||
mockNewDoc.save.mockRejectedValue(new Error('Save failed'));
|
||||
state.files = [
|
||||
{
|
||||
name: 'z.pdf',
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: vi.fn().mockResolvedValue(new ArrayBuffer(8)),
|
||||
},
|
||||
];
|
||||
|
||||
await reversePages();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith(
|
||||
'Error',
|
||||
'Could not reverse the PDF pages.'
|
||||
);
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,14 @@ describe('Tool Categories Configuration', () => {
|
||||
|
||||
// **KEY CHANGE**: This test now ensures IDs are unique only WITHIN this specific category.
|
||||
it('should not contain any duplicate tool IDs within its own list', () => {
|
||||
const toolIds = category.tools.map((tool) => tool.id);
|
||||
const toolIds = category.tools.map((tool) => {
|
||||
if ('id' in tool) return (tool as any).id;
|
||||
if ('href' in tool) {
|
||||
const match = (tool as any).href.match(/\/([^/]+)\.html$/);
|
||||
return match ? match[1] : (tool as any).href;
|
||||
}
|
||||
return 'unknown';
|
||||
});
|
||||
const uniqueToolIds = new Set(toolIds);
|
||||
|
||||
// This assertion checks for duplicates inside THIS category only.
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { txtToPdf, setupTxtToPdfTool } from '@/js/logic/txt-to-pdf';
|
||||
import * as ui from '@/js/ui';
|
||||
import * as helpers from '@/js/utils/helpers';
|
||||
import { state } from '@/js/state';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
|
||||
// Mocks
|
||||
vi.mock('@/js/ui', () => ({
|
||||
showLoader: vi.fn(),
|
||||
hideLoader: vi.fn(),
|
||||
showAlert: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/js/utils/helpers', () => ({
|
||||
downloadFile: vi.fn(),
|
||||
hexToRgb: vi.fn(() => ({ r: 0, g: 0, b: 0 })),
|
||||
}));
|
||||
|
||||
vi.mock('pdf-lib', () => ({
|
||||
PDFDocument: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
StandardFonts: {
|
||||
Helvetica: 'Helvetica',
|
||||
},
|
||||
PageSizes: {
|
||||
A4: [595.28, 841.89],
|
||||
},
|
||||
rgb: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('jszip', () => {
|
||||
return {
|
||||
default: vi.fn().mockImplementation(() => ({
|
||||
file: vi.fn(),
|
||||
generateAsync: vi.fn().mockResolvedValue(new Blob()),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Text to PDF Tool', () => {
|
||||
let mockPdfDoc: any;
|
||||
let mockPage: any;
|
||||
let mockFont: any;
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<button id="txt-mode-upload-btn"></button>
|
||||
<button id="txt-mode-text-btn"></button>
|
||||
<div id="txt-upload-panel"></div>
|
||||
<div id="txt-text-panel" class="hidden"></div>
|
||||
<button id="process-btn"></button>
|
||||
<select id="font-family"><option value="Helvetica">Helvetica</option></select>
|
||||
<input id="font-size" value="12" />
|
||||
<select id="page-size"><option value="A4">A4</option></select>
|
||||
<input id="text-color" value="#000000" />
|
||||
<textarea id="text-input"></textarea>
|
||||
`;
|
||||
|
||||
mockPage = {
|
||||
getSize: vi.fn(() => ({ width: 595.28, height: 841.89 })),
|
||||
drawText: vi.fn(),
|
||||
getHeight: vi.fn(() => 841.89),
|
||||
};
|
||||
|
||||
mockFont = {
|
||||
widthOfTextAtSize: vi.fn(() => 10),
|
||||
};
|
||||
|
||||
mockPdfDoc = {
|
||||
embedFont: vi.fn().mockResolvedValue(mockFont),
|
||||
addPage: vi.fn(() => mockPage),
|
||||
save: vi.fn().mockResolvedValue(new Uint8Array([])),
|
||||
};
|
||||
|
||||
vi.mocked(PDFLibDocument.create).mockResolvedValue(mockPdfDoc);
|
||||
state.files = [];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('setupTxtToPdfTool should attach onclick listener to process-btn', async () => {
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
expect(processBtn?.onclick).toBeNull();
|
||||
|
||||
await setupTxtToPdfTool();
|
||||
|
||||
expect(processBtn?.onclick).toBeDefined();
|
||||
});
|
||||
|
||||
it('txtToPdf should handle text input mode', async () => {
|
||||
// Switch to text mode (simulate UI state)
|
||||
document.getElementById('txt-upload-panel')?.classList.add('hidden');
|
||||
document.getElementById('txt-text-panel')?.classList.remove('hidden');
|
||||
(document.getElementById('text-input') as HTMLTextAreaElement).value = 'Hello World';
|
||||
|
||||
await txtToPdf();
|
||||
|
||||
expect(ui.showLoader).toHaveBeenCalled();
|
||||
expect(PDFLibDocument.create).toHaveBeenCalled();
|
||||
expect(mockPdfDoc.addPage).toHaveBeenCalled();
|
||||
expect(mockPage.drawText).toHaveBeenCalled();
|
||||
expect(helpers.downloadFile).toHaveBeenCalled();
|
||||
expect(ui.hideLoader).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('txtToPdf should show alert if text input is empty', async () => {
|
||||
document.getElementById('txt-upload-panel')?.classList.add('hidden');
|
||||
document.getElementById('txt-text-panel')?.classList.remove('hidden');
|
||||
(document.getElementById('text-input') as HTMLTextAreaElement).value = ' ';
|
||||
|
||||
await txtToPdf();
|
||||
|
||||
expect(ui.showAlert).toHaveBeenCalledWith('Input Required', 'Please enter some text to convert.');
|
||||
expect(PDFLibDocument.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user