mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-23 23:10:17 +05:30
feat(pdf): add form field detection, fill, and flatten
Adds pdfGetFormFields (lists AcroForm fields with name/type/value) and pdfFillForm (fills text fields by name, optionally flattens) to PDFOperations.js, dispatched via 'formFields'/'fillForm' in executeOperation. pdfFillForm skips unknown/non-text fields per-field (logs + continues) rather than failing the whole batch, matching the partial-success precedent set by pdfExtractImages. Wires a "Fill Form" entry into the PDF editor dialog: selecting a PDF fetches its fields via a new get-pdf-form-fields/pdf-form-fields IPC round trip and renders one text input per field, plus a flatten checkbox, following the same structure as the crop/pageNumbers dialogs. Amit Haridas
This commit is contained in:
@@ -2111,6 +2111,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Fill Form Section -->
|
||||||
|
<div id="pdf-fill-form-section" class="pdf-operation-section hidden">
|
||||||
|
<div class="export-section">
|
||||||
|
<label>PDF File:</label>
|
||||||
|
<div class="folder-input-group">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="fill-form-input-path"
|
||||||
|
placeholder="Select PDF file..."
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<button id="browse-fill-form-input">Browse</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="export-section">
|
||||||
|
<label>Form Fields:</label>
|
||||||
|
<div id="fill-form-fields-list" class="fill-form-fields-list">
|
||||||
|
<small>Select a PDF with fillable fields to list them here.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="export-section">
|
||||||
|
<label class="checkbox-inline"
|
||||||
|
><input type="checkbox" id="fill-form-flatten" /> Flatten after fill (makes
|
||||||
|
fields non-editable)</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="export-section">
|
||||||
|
<label class="checkbox-inline"
|
||||||
|
><input type="checkbox" id="fill-form-overwrite" /> Overwrite original
|
||||||
|
file</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="export-section" id="fill-form-saveas-section">
|
||||||
|
<label>Save As:</label>
|
||||||
|
<div class="folder-input-group">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="fill-form-output-path"
|
||||||
|
placeholder="Select save location..."
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<button id="browse-fill-form-output">Save As</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="pdf-status-message" class="info-message hidden" aria-live="polite"></div>
|
<div id="pdf-status-message" class="info-message hidden" aria-live="polite"></div>
|
||||||
|
|
||||||
<!-- Progress indicator -->
|
<!-- Progress indicator -->
|
||||||
|
|||||||
+18
@@ -1454,6 +1454,13 @@ function createMenu() {
|
|||||||
{
|
{
|
||||||
type: 'separator',
|
type: 'separator',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Fill Form...',
|
||||||
|
click: () => showPDFEditorDialog('fillForm'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Security',
|
label: 'Security',
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -4699,6 +4706,17 @@ ipcMain.on('get-pdf-page-count', async (event, filePath) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
ipcMain.on('get-pdf-form-fields', async (event, filePath) => {
|
||||||
|
try {
|
||||||
|
const result = await PDFOperations.pdfGetFormFields({ inputPath: filePath });
|
||||||
|
event.reply('pdf-form-fields', result);
|
||||||
|
} catch (error) {
|
||||||
|
event.reply('pdf-form-fields', {
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// IPC Handler for folder selection (for PDF operations)
|
// IPC Handler for folder selection (for PDF operations)
|
||||||
ipcMain.on('select-pdf-folder', (event, inputId) => {
|
ipcMain.on('select-pdf-folder', (event, inputId) => {
|
||||||
|
|||||||
@@ -589,6 +589,70 @@ async function pdfExtractImages(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function pdfGetFormFields(data) {
|
||||||
|
try {
|
||||||
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
||||||
|
const pdf = await PDFDocument.load(pdfBytes);
|
||||||
|
const form = pdf.getForm();
|
||||||
|
|
||||||
|
const fields = form.getFields().map((field) => {
|
||||||
|
let value;
|
||||||
|
try {
|
||||||
|
if (typeof field.getText === 'function') {
|
||||||
|
value = field.getText();
|
||||||
|
} else if (typeof field.isChecked === 'function') {
|
||||||
|
value = field.isChecked();
|
||||||
|
} else if (typeof field.getSelected === 'function') {
|
||||||
|
value = field.getSelected();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Some field types throw when read in an unexpected state; leave value undefined.
|
||||||
|
value = undefined;
|
||||||
|
}
|
||||||
|
return { name: field.getName(), type: field.constructor.name, value };
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, fields };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pdfFillForm(data) {
|
||||||
|
try {
|
||||||
|
const pdfBytes = fs.readFileSync(data.inputPath);
|
||||||
|
const pdf = await PDFDocument.load(pdfBytes);
|
||||||
|
const form = pdf.getForm();
|
||||||
|
|
||||||
|
const values = data.values || {};
|
||||||
|
let filledCount = 0;
|
||||||
|
|
||||||
|
for (const [name, value] of Object.entries(values)) {
|
||||||
|
try {
|
||||||
|
const field = form.getTextField(name);
|
||||||
|
field.setText(value !== null && value !== undefined ? String(value) : '');
|
||||||
|
filledCount++;
|
||||||
|
} catch (fieldError) {
|
||||||
|
// Batch-of-independent-fields: a field that doesn't exist or isn't a text
|
||||||
|
// field shouldn't fail the whole fill — skip it and keep going (same
|
||||||
|
// partial-success precedent as pdfExtractImages).
|
||||||
|
console.warn(`pdfFillForm: skipping field "${name}": ${fieldError.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.flatten) {
|
||||||
|
form.flatten();
|
||||||
|
}
|
||||||
|
|
||||||
|
const filledPdfBytes = await pdf.save();
|
||||||
|
fs.writeFileSync(data.outputPath, filledPdfBytes);
|
||||||
|
|
||||||
|
return { success: true, message: `Successfully filled ${filledCount} form field(s)` };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function executeOperation(operation, data) {
|
function executeOperation(operation, data) {
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case 'merge':
|
case 'merge':
|
||||||
@@ -619,6 +683,10 @@ function executeOperation(operation, data) {
|
|||||||
return pdfCrop(data);
|
return pdfCrop(data);
|
||||||
case 'extractImages':
|
case 'extractImages':
|
||||||
return pdfExtractImages(data);
|
return pdfExtractImages(data);
|
||||||
|
case 'formFields':
|
||||||
|
return pdfGetFormFields(data);
|
||||||
|
case 'fillForm':
|
||||||
|
return pdfFillForm(data);
|
||||||
default:
|
default:
|
||||||
return Promise.resolve({ success: false, error: `Unknown operation: ${operation}` });
|
return Promise.resolve({ success: false, error: `Unknown operation: ${operation}` });
|
||||||
}
|
}
|
||||||
@@ -647,6 +715,8 @@ module.exports = {
|
|||||||
pdfAddPageNumbers,
|
pdfAddPageNumbers,
|
||||||
pdfCrop,
|
pdfCrop,
|
||||||
pdfExtractImages,
|
pdfExtractImages,
|
||||||
|
pdfGetFormFields,
|
||||||
|
pdfFillForm,
|
||||||
executeOperation,
|
executeOperation,
|
||||||
getPageCount,
|
getPageCount,
|
||||||
};
|
};
|
||||||
|
|||||||
+108
@@ -3894,6 +3894,23 @@ function showPDFEditorDialog(operation, openedFilePath = null) {
|
|||||||
if (extractImagesInput) extractImagesInput.value = openedFilePath;
|
if (extractImagesInput) extractImagesInput.value = openedFilePath;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'fillForm': {
|
||||||
|
sectionId = 'pdf-fill-form-section';
|
||||||
|
titleText = 'Fill Form';
|
||||||
|
const fieldsList = document.getElementById('fill-form-fields-list');
|
||||||
|
if (fieldsList) {
|
||||||
|
fieldsList.innerHTML =
|
||||||
|
'<small>Select a PDF with fillable fields to list them here.</small>';
|
||||||
|
}
|
||||||
|
if (openedFilePath) {
|
||||||
|
const fillFormInput = document.getElementById('fill-form-input-path');
|
||||||
|
if (fillFormInput) {
|
||||||
|
fillFormInput.value = openedFilePath;
|
||||||
|
setTimeout(() => loadFillFormFields(openedFilePath), 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
title.textContent = titleText;
|
title.textContent = titleText;
|
||||||
document.getElementById(sectionId).classList.remove('hidden');
|
document.getElementById(sectionId).classList.remove('hidden');
|
||||||
@@ -4112,6 +4129,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
inputId: 'extract-images-output-folder',
|
inputId: 'extract-images-output-folder',
|
||||||
folder: true,
|
folder: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'browse-fill-form-input',
|
||||||
|
inputId: 'fill-form-input-path',
|
||||||
|
saveDialog: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'browse-fill-form-output',
|
||||||
|
inputId: 'fill-form-output-path',
|
||||||
|
saveDialog: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
browseButtons.forEach((button) => {
|
browseButtons.forEach((button) => {
|
||||||
const btn = document.getElementById(button.id);
|
const btn = document.getElementById(button.id);
|
||||||
@@ -4140,6 +4167,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (file) {
|
if (file) {
|
||||||
document.getElementById(button.inputId).value = file.path;
|
document.getElementById(button.inputId).value = file.path;
|
||||||
onPDFFileSelected(button.inputId, file.path);
|
onPDFFileSelected(button.inputId, file.path);
|
||||||
|
if (button.inputId === 'fill-form-input-path') {
|
||||||
|
loadFillFormFields(file.path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
@@ -4231,6 +4261,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
checkbox: 'crop-overwrite',
|
checkbox: 'crop-overwrite',
|
||||||
section: 'crop-saveas-section',
|
section: 'crop-saveas-section',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
checkbox: 'fill-form-overwrite',
|
||||||
|
section: 'fill-form-saveas-section',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
overwriteCheckboxes.forEach((item) => {
|
overwriteCheckboxes.forEach((item) => {
|
||||||
const checkbox = document.getElementById(item.checkbox);
|
const checkbox = document.getElementById(item.checkbox);
|
||||||
@@ -4285,6 +4319,59 @@ ipcRenderer.on('pdf-page-count', (event, { count, error }) => {
|
|||||||
document.getElementById('current-page-order').classList.remove('hidden');
|
document.getElementById('current-page-order').classList.remove('hidden');
|
||||||
document.getElementById('reorder-pages').value = currentOrder;
|
document.getElementById('reorder-pages').value = currentOrder;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fill Form: request the AcroForm text fields for a selected PDF, then render
|
||||||
|
// one text input per field so the user can supply values before submitting.
|
||||||
|
function loadFillFormFields(filePath) {
|
||||||
|
const container = document.getElementById('fill-form-fields-list');
|
||||||
|
if (!container) return;
|
||||||
|
container.innerHTML = '';
|
||||||
|
const loading = document.createElement('small');
|
||||||
|
loading.textContent = 'Loading form fields...';
|
||||||
|
container.appendChild(loading);
|
||||||
|
ipcRenderer.send('get-pdf-form-fields', filePath);
|
||||||
|
}
|
||||||
|
ipcRenderer.on('pdf-form-fields', (event, result) => {
|
||||||
|
const container = document.getElementById('fill-form-fields-list');
|
||||||
|
if (!container) return;
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
const msg = document.createElement('small');
|
||||||
|
msg.textContent = `Error reading form fields: ${result.error || 'unknown error'}`;
|
||||||
|
container.appendChild(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textFields = (result.fields || []).filter((field) => field.type === 'PDFTextField');
|
||||||
|
|
||||||
|
if (textFields.length === 0) {
|
||||||
|
const msg = document.createElement('small');
|
||||||
|
msg.textContent = 'No fillable text fields were found in this PDF.';
|
||||||
|
container.appendChild(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
textFields.forEach((field) => {
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'fill-form-field-row';
|
||||||
|
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.textContent = field.name;
|
||||||
|
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'text';
|
||||||
|
input.className = 'fill-form-field-input';
|
||||||
|
input.dataset.fieldName = field.name;
|
||||||
|
if (field.value) {
|
||||||
|
input.value = field.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.appendChild(input);
|
||||||
|
row.appendChild(label);
|
||||||
|
container.appendChild(row);
|
||||||
|
});
|
||||||
|
});
|
||||||
function getPDFStatusElement() {
|
function getPDFStatusElement() {
|
||||||
return document.getElementById('pdf-status-message');
|
return document.getElementById('pdf-status-message');
|
||||||
}
|
}
|
||||||
@@ -4573,6 +4660,27 @@ function processPDFOperation() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'fillForm':
|
||||||
|
operationData.inputPath = document.getElementById('fill-form-input-path').value.trim();
|
||||||
|
operationData.overwrite = document.getElementById('fill-form-overwrite').checked;
|
||||||
|
operationData.outputPath = operationData.overwrite
|
||||||
|
? operationData.inputPath
|
||||||
|
: document.getElementById('fill-form-output-path').value.trim();
|
||||||
|
operationData.flatten = document.getElementById('fill-form-flatten').checked;
|
||||||
|
operationData.values = {};
|
||||||
|
document
|
||||||
|
.querySelectorAll('#fill-form-fields-list .fill-form-field-input')
|
||||||
|
.forEach((input) => {
|
||||||
|
operationData.values[input.dataset.fieldName] = input.value;
|
||||||
|
});
|
||||||
|
if (!operationData.inputPath || !operationData.outputPath) {
|
||||||
|
showPDFValidationMessage(
|
||||||
|
'Select an input PDF' + (operationData.overwrite ? '.' : ' and an output file path.'),
|
||||||
|
'#fill-form-input-path'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
clearPDFStatus();
|
clearPDFStatus();
|
||||||
// Show progress
|
// Show progress
|
||||||
|
|||||||
@@ -230,3 +230,158 @@ describe('PDFOperations - Task 15 new operations', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PDFOperations - Task 16 form field fill/flatten', () => {
|
||||||
|
let tmpDir, plainInputPath;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pdfops_form_'));
|
||||||
|
plainInputPath = path.join(tmpDir, 'plain.pdf');
|
||||||
|
|
||||||
|
const doc = await PDFDocument.create();
|
||||||
|
doc.addPage([600, 800]);
|
||||||
|
fs.writeFileSync(plainInputPath, await doc.save());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Builds a fixture PDF with a real AcroForm text field via pdf-lib's
|
||||||
|
// form.createTextField() API, mirroring pdf-lib's documented form-creation flow.
|
||||||
|
async function buildFormPdf(fileName, initialValue = 'John Doe') {
|
||||||
|
const doc = await PDFDocument.create();
|
||||||
|
const page = doc.addPage([600, 800]);
|
||||||
|
const form = doc.getForm();
|
||||||
|
const nameField = form.createTextField('name');
|
||||||
|
nameField.setText(initialValue);
|
||||||
|
nameField.addToPage(page, { x: 50, y: 700, width: 200, height: 20 });
|
||||||
|
|
||||||
|
const filePath = path.join(tmpDir, fileName);
|
||||||
|
fs.writeFileSync(filePath, await doc.save());
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('pdfGetFormFields', () => {
|
||||||
|
it('lists text fields with name, type, and current value', async () => {
|
||||||
|
const formPath = await buildFormPdf('form.pdf', 'John Doe');
|
||||||
|
|
||||||
|
const result = await PDFOperations.pdfGetFormFields({ inputPath: formPath });
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.fields).toEqual([{ name: 'name', type: 'PDFTextField', value: 'John Doe' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty fields array for a PDF with no AcroForm', async () => {
|
||||||
|
const result = await PDFOperations.pdfGetFormFields({ inputPath: plainInputPath });
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.fields).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns failure for a nonexistent file', async () => {
|
||||||
|
const result = await PDFOperations.pdfGetFormFields({
|
||||||
|
inputPath: path.join(tmpDir, 'missing.pdf'),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(result.error).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pdfFillForm', () => {
|
||||||
|
it('fills a text field with the given value', async () => {
|
||||||
|
const formPath = await buildFormPdf('form.pdf', '');
|
||||||
|
const outputPath = path.join(tmpDir, 'filled.pdf');
|
||||||
|
|
||||||
|
const result = await PDFOperations.pdfFillForm({
|
||||||
|
inputPath: formPath,
|
||||||
|
outputPath,
|
||||||
|
values: { name: 'Jane Smith' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(fs.existsSync(outputPath)).toBe(true);
|
||||||
|
|
||||||
|
const filled = await PDFDocument.load(fs.readFileSync(outputPath));
|
||||||
|
expect(filled.getForm().getTextField('name').getText()).toBe('Jane Smith');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('flattens the form when flatten is true, removing editable fields', async () => {
|
||||||
|
const formPath = await buildFormPdf('form.pdf', '');
|
||||||
|
const outputPath = path.join(tmpDir, 'flattened.pdf');
|
||||||
|
|
||||||
|
const result = await PDFOperations.pdfFillForm({
|
||||||
|
inputPath: formPath,
|
||||||
|
outputPath,
|
||||||
|
values: { name: 'Jane Smith' },
|
||||||
|
flatten: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
const flattened = await PDFDocument.load(fs.readFileSync(outputPath));
|
||||||
|
expect(flattened.getForm().getFields().length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not flatten when flatten is false/omitted', async () => {
|
||||||
|
const formPath = await buildFormPdf('form.pdf', '');
|
||||||
|
const outputPath = path.join(tmpDir, 'not-flattened.pdf');
|
||||||
|
|
||||||
|
const result = await PDFOperations.pdfFillForm({
|
||||||
|
inputPath: formPath,
|
||||||
|
outputPath,
|
||||||
|
values: { name: 'Jane Smith' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
const notFlattened = await PDFDocument.load(fs.readFileSync(outputPath));
|
||||||
|
expect(notFlattened.getForm().getFields().length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips a value for a field that does not exist, continuing with the rest', async () => {
|
||||||
|
const formPath = await buildFormPdf('form.pdf', '');
|
||||||
|
const outputPath = path.join(tmpDir, 'filled-partial.pdf');
|
||||||
|
|
||||||
|
const result = await PDFOperations.pdfFillForm({
|
||||||
|
inputPath: formPath,
|
||||||
|
outputPath,
|
||||||
|
values: { name: 'Jane Smith', doesNotExist: 'whatever' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
const filled = await PDFDocument.load(fs.readFileSync(outputPath));
|
||||||
|
expect(filled.getForm().getTextField('name').getText()).toBe('Jane Smith');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns failure for a nonexistent input file', async () => {
|
||||||
|
const result = await PDFOperations.pdfFillForm({
|
||||||
|
inputPath: path.join(tmpDir, 'missing.pdf'),
|
||||||
|
outputPath: path.join(tmpDir, 'out.pdf'),
|
||||||
|
values: { name: 'X' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(result.error).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('executeOperation dispatch', () => {
|
||||||
|
it('dispatches formFields', async () => {
|
||||||
|
const formPath = await buildFormPdf('form.pdf', 'John Doe');
|
||||||
|
const result = await PDFOperations.executeOperation('formFields', { inputPath: formPath });
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.fields.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches fillForm', async () => {
|
||||||
|
const formPath = await buildFormPdf('form.pdf', '');
|
||||||
|
const outputPath = path.join(tmpDir, 'dispatch-filled.pdf');
|
||||||
|
const result = await PDFOperations.executeOperation('fillForm', {
|
||||||
|
inputPath: formPath,
|
||||||
|
outputPath,
|
||||||
|
values: { name: 'Dispatch Test' },
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user