mirror of
https://github.com/pdfme/pdfme.git
synced 2026-04-17 20:49:43 -04:00
363 lines
11 KiB
TypeScript
363 lines
11 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import examplesCmd from '../src/commands/examples.js';
|
|
import {
|
|
fetchExampleTemplate,
|
|
getExampleManifest,
|
|
getExampleTemplateNames,
|
|
} from '../src/example-templates.js';
|
|
import { OFFICIAL_EXAMPLE_FONT_URLS } from '../src/example-fonts.js';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const TMP = join(__dirname, '..', '.test-tmp-examples');
|
|
|
|
describe('examples command', () => {
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
vi.restoreAllMocks();
|
|
delete process.env.PDFME_EXAMPLES_BASE_URL;
|
|
rmSync(TMP, { recursive: true, force: true });
|
|
});
|
|
|
|
it('fetches manifest.json for --list output', async () => {
|
|
const fetchMock = vi.fn(async (input: string | URL | Request) => {
|
|
expect(String(input)).toBe('https://playground.pdfme.com/template-assets/manifest.json');
|
|
return new Response(
|
|
JSON.stringify({
|
|
schemaVersion: 1,
|
|
cliVersion: '0.1.0-alpha.0',
|
|
templates: [{ name: 'zeta' }, { name: 'alpha' }],
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: { 'content-type': 'application/json' },
|
|
},
|
|
);
|
|
});
|
|
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
|
|
await examplesCmd.run!({
|
|
args: { list: true, name: undefined, json: false },
|
|
rawArgs: [],
|
|
cmd: examplesCmd,
|
|
} as never);
|
|
|
|
expect(logSpy.mock.calls.map(([message]) => message)).toEqual([
|
|
'Available templates:',
|
|
' alpha',
|
|
' zeta',
|
|
]);
|
|
});
|
|
|
|
it('falls back to index.json when manifest.json is unavailable', async () => {
|
|
process.env.PDFME_EXAMPLES_BASE_URL = 'https://fixtures.example.com/template-assets';
|
|
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async (input: string | URL | Request) => {
|
|
const url = String(input);
|
|
|
|
if (url.endsWith('/manifest.json')) {
|
|
return new Response('missing', { status: 404 });
|
|
}
|
|
|
|
if (url.endsWith('/index.json')) {
|
|
return new Response(JSON.stringify([{ name: 'invoice', author: 'pdfme' }]), {
|
|
status: 200,
|
|
headers: { 'content-type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
throw new Error(`Unexpected URL: ${url}`);
|
|
}),
|
|
);
|
|
|
|
const manifest = await getExampleManifest();
|
|
expect(manifest.source).toBe('remote');
|
|
expect(manifest.url).toBe('https://fixtures.example.com/template-assets/index.json');
|
|
expect(manifest.manifest.templates).toEqual([
|
|
{
|
|
name: 'invoice',
|
|
author: 'pdfme',
|
|
path: 'invoice/template.json',
|
|
thumbnailPath: 'invoice/thumbnail.png',
|
|
pageCount: 0,
|
|
fieldCount: 0,
|
|
schemaTypes: [],
|
|
fontNames: [],
|
|
hasCJK: false,
|
|
basePdfKind: 'unknown',
|
|
},
|
|
]);
|
|
|
|
const names = await getExampleTemplateNames();
|
|
expect(names).toEqual(['invoice']);
|
|
});
|
|
|
|
it('normalizes manifest entries to a complete metadata shape', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () =>
|
|
new Response(
|
|
JSON.stringify({
|
|
schemaVersion: 1,
|
|
cliVersion: '0.1.0-alpha.0',
|
|
templates: [{ name: 'invoice' }],
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: { 'content-type': 'application/json' },
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
const manifest = await getExampleManifest();
|
|
expect(manifest.manifest.templates).toEqual([
|
|
{
|
|
name: 'invoice',
|
|
author: 'pdfme',
|
|
path: 'invoice/template.json',
|
|
thumbnailPath: 'invoice/thumbnail.png',
|
|
pageCount: 0,
|
|
fieldCount: 0,
|
|
schemaTypes: [],
|
|
fontNames: [],
|
|
hasCJK: false,
|
|
basePdfKind: 'unknown',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('fetches a template referenced from the manifest', async () => {
|
|
process.env.PDFME_EXAMPLES_BASE_URL = 'https://fixtures.example.com/template-assets';
|
|
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async (input: string | URL | Request) => {
|
|
const url = String(input);
|
|
|
|
if (url.endsWith('/manifest.json')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
schemaVersion: 1,
|
|
cliVersion: '0.1.0-alpha.0',
|
|
templates: [{ name: 'invoice', path: 'invoice/template.json' }],
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
);
|
|
}
|
|
|
|
if (url.endsWith('/invoice/template.json')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
basePdf: { width: 210, height: 297, padding: [20, 20, 20, 20] },
|
|
schemas: [[{ name: 'title', type: 'text' }]],
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
);
|
|
}
|
|
|
|
throw new Error(`Unexpected URL: ${url}`);
|
|
}),
|
|
);
|
|
|
|
const template = await fetchExampleTemplate('invoice');
|
|
expect(template).toEqual({
|
|
basePdf: { width: 210, height: 297, padding: [20, 20, 20, 20] },
|
|
schemas: [[{ name: 'title', type: 'text' }]],
|
|
});
|
|
});
|
|
|
|
it('writes output files and emits structured JSON', async () => {
|
|
process.env.PDFME_EXAMPLES_BASE_URL = 'https://fixtures.example.com/template-assets';
|
|
mkdirSync(TMP, { recursive: true });
|
|
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async (input: string | URL | Request) => {
|
|
const url = String(input);
|
|
|
|
if (url.endsWith('/manifest.json')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
schemaVersion: 1,
|
|
cliVersion: '0.1.0-alpha.0',
|
|
templates: [{ name: 'invoice', path: 'invoice/template.json' }],
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
);
|
|
}
|
|
|
|
if (url.endsWith('/invoice/template.json')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
basePdf: { width: 210, height: 297, padding: [20, 20, 20, 20] },
|
|
schemas: [[{ name: 'title', type: 'text' }]],
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
);
|
|
}
|
|
|
|
throw new Error(`Unexpected URL: ${url}`);
|
|
}),
|
|
);
|
|
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
const outputPath = join(TMP, 'job.json');
|
|
|
|
await examplesCmd.run!({
|
|
args: {
|
|
list: false,
|
|
name: 'invoice',
|
|
output: outputPath,
|
|
withInputs: true,
|
|
json: true,
|
|
},
|
|
rawArgs: [],
|
|
cmd: examplesCmd,
|
|
} as never);
|
|
|
|
const payload = JSON.parse(String(logSpy.mock.calls.at(-1)?.[0] ?? 'null'));
|
|
expect(payload.ok).toBe(true);
|
|
expect(payload.source).toBe('remote');
|
|
expect(payload.outputPath).toBe(outputPath);
|
|
|
|
const written = JSON.parse(readFileSync(outputPath, 'utf8'));
|
|
expect(written.inputs).toEqual([{ title: 'Sample title' }]);
|
|
});
|
|
|
|
it('includes editable fields from every template page in generated sample inputs', async () => {
|
|
process.env.PDFME_EXAMPLES_BASE_URL = 'https://fixtures.example.com/template-assets';
|
|
mkdirSync(TMP, { recursive: true });
|
|
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async (input: string | URL | Request) => {
|
|
const url = String(input);
|
|
|
|
if (url.endsWith('/manifest.json')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
schemaVersion: 1,
|
|
cliVersion: '0.1.0-alpha.0',
|
|
templates: [{ name: 'multi-page', path: 'multi-page/template.json' }],
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
);
|
|
}
|
|
|
|
if (url.endsWith('/multi-page/template.json')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
basePdf: { width: 210, height: 297, padding: [20, 20, 20, 20] },
|
|
schemas: [
|
|
[
|
|
{ name: 'page1', type: 'text', content: 'Page 1' },
|
|
{ name: 'readonly', type: 'text', content: 'Locked', readOnly: true },
|
|
],
|
|
{
|
|
page2: { type: 'text', content: 'Page 2' },
|
|
page2Readonly: { type: 'text', content: 'Locked 2', readOnly: true },
|
|
},
|
|
[{ name: 'page3', type: 'text' }],
|
|
],
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
);
|
|
}
|
|
|
|
throw new Error(`Unexpected URL: ${url}`);
|
|
}),
|
|
);
|
|
|
|
const outputPath = join(TMP, 'multi-page-job.json');
|
|
|
|
await examplesCmd.run!({
|
|
args: {
|
|
list: false,
|
|
name: 'multi-page',
|
|
output: outputPath,
|
|
withInputs: true,
|
|
json: true,
|
|
},
|
|
rawArgs: [],
|
|
cmd: examplesCmd,
|
|
} as never);
|
|
|
|
const written = JSON.parse(readFileSync(outputPath, 'utf8'));
|
|
expect(written.inputs).toEqual([{ page1: 'Page 1', page2: 'Page 2', page3: 'Sample page3' }]);
|
|
});
|
|
|
|
it('embeds official example font URLs into unified jobs', async () => {
|
|
process.env.PDFME_EXAMPLES_BASE_URL = 'https://fixtures.example.com/template-assets';
|
|
mkdirSync(TMP, { recursive: true });
|
|
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async (input: string | URL | Request) => {
|
|
const url = String(input);
|
|
|
|
if (url.endsWith('/manifest.json')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
schemaVersion: 1,
|
|
cliVersion: '0.1.0-alpha.0',
|
|
templates: [{ name: 'certificate-black', path: 'certificate-black/template.json' }],
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
);
|
|
}
|
|
|
|
if (url.endsWith('/certificate-black/template.json')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
basePdf: { width: 210, height: 297, padding: [20, 20, 20, 20] },
|
|
schemas: [[
|
|
{
|
|
name: 'signature',
|
|
type: 'text',
|
|
fontName: 'PinyonScript-Regular',
|
|
position: { x: 20, y: 20 },
|
|
width: 100,
|
|
height: 20,
|
|
},
|
|
]],
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
);
|
|
}
|
|
|
|
throw new Error(`Unexpected URL: ${url}`);
|
|
}),
|
|
);
|
|
|
|
const outputPath = join(TMP, 'certificate-job.json');
|
|
|
|
await examplesCmd.run!({
|
|
args: {
|
|
list: false,
|
|
name: 'certificate-black',
|
|
output: outputPath,
|
|
withInputs: true,
|
|
json: true,
|
|
},
|
|
rawArgs: [],
|
|
cmd: examplesCmd,
|
|
} as never);
|
|
|
|
const written = JSON.parse(readFileSync(outputPath, 'utf8'));
|
|
expect(written.options.font).toEqual({
|
|
'PinyonScript-Regular': {
|
|
data: OFFICIAL_EXAMPLE_FONT_URLS['PinyonScript-Regular'],
|
|
fallback: false,
|
|
subset: true,
|
|
},
|
|
});
|
|
});
|
|
});
|