mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-10 17:14:34 +01:00
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:
1 parent
d4633e1429
commit
39080c800f
7 files changed
+374
-2
No files matched your search
@@ -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', () => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user