feat: add remove blank pages tool with sensitivity control

Implement new tool to detect and remove blank pages from PDFs with configurable sensitivity. Includes UI with preview functionality and comprehensive tests.
This commit is contained in:
abdullahalam123
2025-10-15 23:34:53 +05:30
parent d4633e1429
commit 39080c800f
7 changed files with 374 additions and 2 deletions

View File

@@ -16,7 +16,7 @@ describe('Tool Configuration Arrays', () => {
it('should have the correct number of tools', () => {
// This acts as a snapshot test to catch unexpected additions/removals.
expect(singlePdfLoadTools).toHaveLength(38);
expect(singlePdfLoadTools).toHaveLength(39);
});
it('should not contain any duplicate tools', () => {

View File

@@ -0,0 +1,190 @@
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();
});
});