setup i18n and ported all tools to standalone pages

This commit is contained in:
abdullahalam123
2025-12-11 19:34:14 +05:30
parent fe3e54f979
commit 78dc6333f9
221 changed files with 30351 additions and 11131 deletions

View File

@@ -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'
);
});
});
});

View File

@@ -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();
});
});
});

View File

@@ -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();
});
});

View File

@@ -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();
});
});

View File

@@ -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.

View File

@@ -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();
});
});