mirror of
https://github.com/pdfme/pdfme.git
synced 2026-06-16 02:09:10 -04:00
469 lines
14 KiB
TypeScript
469 lines
14 KiB
TypeScript
import React from 'react';
|
|
import { render, act, fireEvent, waitFor } from '@testing-library/react';
|
|
import Preview from '../../src/components/Preview';
|
|
import { I18nContext, FontContext, OptionsContext, PluginsRegistry } from '../../src/contexts';
|
|
import { i18n } from '../../src/i18n';
|
|
import { SELECTABLE_CLASSNAME } from '../../src/constants';
|
|
import {
|
|
CUSTOM_A4_PDF,
|
|
PAGE_SIZE_PRESETS,
|
|
ZOOM,
|
|
getDefaultFont,
|
|
pluginRegistry,
|
|
type Plugin,
|
|
type Template,
|
|
} from '@pdfme/common';
|
|
import { normalizeElementIdsForSnapshot } from '../assets/normalizeSnapshot';
|
|
import {
|
|
getSampleTemplate,
|
|
getTwoPageTemplate,
|
|
mockClientSizeFromStyle,
|
|
setupUIMock,
|
|
} from '../assets/helper';
|
|
import { text, image } from '@pdfme/schemas';
|
|
|
|
const plugins = pluginRegistry({ text, image });
|
|
|
|
const getScrollContainer = (container: HTMLElement) => {
|
|
const scrollContainer = Array.from(container.querySelectorAll('div')).find(
|
|
(element) => element.style.overflow === 'auto' && element.style.position === 'relative',
|
|
);
|
|
if (!(scrollContainer instanceof HTMLDivElement)) {
|
|
throw new Error('Scroll container was not found');
|
|
}
|
|
return scrollContainer;
|
|
};
|
|
|
|
let restoreClientSizeMock: (() => void) | undefined;
|
|
|
|
afterEach(() => {
|
|
restoreClientSizeMock?.();
|
|
restoreClientSizeMock = undefined;
|
|
});
|
|
|
|
const createTouchList = (items: Array<{ clientX: number; clientY: number }>) =>
|
|
Object.assign(items, {
|
|
item: (index: number) => items[index] ?? null,
|
|
}) as unknown as TouchList;
|
|
|
|
const dispatchTouchEvent = (
|
|
element: HTMLElement,
|
|
type: 'touchstart' | 'touchmove' | 'touchend',
|
|
touches: TouchList,
|
|
) => {
|
|
const event = new Event(type, { bubbles: true, cancelable: true }) as TouchEvent;
|
|
Object.defineProperty(event, 'touches', { value: touches });
|
|
element.dispatchEvent(event);
|
|
};
|
|
|
|
const getFormReflowTemplate = (basePdf: Template['basePdf']): Template => ({
|
|
basePdf,
|
|
schemas: [
|
|
[
|
|
{
|
|
name: 'tasks',
|
|
type: 'list',
|
|
content: '[]',
|
|
position: { x: 10, y: 20 },
|
|
width: 60,
|
|
height: 10,
|
|
},
|
|
{
|
|
name: 'footer',
|
|
type: 'text',
|
|
content: '',
|
|
position: { x: 10, y: 35 },
|
|
width: 60,
|
|
height: 10,
|
|
fontSize: 10,
|
|
},
|
|
],
|
|
],
|
|
});
|
|
|
|
const resizingListPlugin: Plugin = {
|
|
pdf: vi.fn(),
|
|
ui: ({ rootElement, onChange }) => {
|
|
const button = document.createElement('button');
|
|
button.type = 'button';
|
|
button.textContent = 'grow list';
|
|
button.addEventListener('click', () => onChange?.({ key: 'height', value: 30 }));
|
|
rootElement.appendChild(button);
|
|
},
|
|
propPanel: {
|
|
schema: {},
|
|
defaultSchema: {
|
|
name: 'tasks',
|
|
type: 'list',
|
|
content: '[]',
|
|
position: { x: 0, y: 0 },
|
|
width: 60,
|
|
height: 10,
|
|
},
|
|
},
|
|
};
|
|
|
|
const formReflowPlugins = pluginRegistry({ list: resizingListPlugin, text });
|
|
|
|
const getTop = (element: Element | null) => {
|
|
if (!(element instanceof HTMLElement)) throw new Error('Element was not found');
|
|
return Number.parseFloat(element.style.top);
|
|
};
|
|
|
|
test('Preview(as Viewer) snapshot', async () => {
|
|
setupUIMock();
|
|
let container: HTMLElement = document.createElement('a');
|
|
act(() => {
|
|
const { container: c } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={plugins}>
|
|
<Preview
|
|
template={getSampleTemplate()}
|
|
inputs={[{ field1: 'field1', field2: 'field2' }]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
/>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
container = c;
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const selectableElements = container.getElementsByClassName(SELECTABLE_CLASSNAME);
|
|
const renderedElements = container.querySelectorAll('[data-pdfme-render-ready="true"]');
|
|
expect(selectableElements.length).toBeGreaterThan(0);
|
|
expect(renderedElements.length).toBe(selectableElements.length);
|
|
});
|
|
expect(normalizeElementIdsForSnapshot(container)).toMatchSnapshot();
|
|
});
|
|
|
|
test('Preview(as Form) snapshot', async () => {
|
|
setupUIMock();
|
|
let container: HTMLElement = document.createElement('a');
|
|
act(() => {
|
|
const { container: c } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={plugins}>
|
|
<Preview
|
|
template={getSampleTemplate()}
|
|
inputs={[{ field1: 'field1', field2: 'field2' }]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
onChangeInput={console.log}
|
|
/>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
container = c;
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const selectableElements = container.getElementsByClassName(SELECTABLE_CLASSNAME);
|
|
const renderedElements = container.querySelectorAll('[data-pdfme-render-ready="true"]');
|
|
expect(selectableElements.length).toBeGreaterThan(0);
|
|
expect(renderedElements.length).toBe(selectableElements.length);
|
|
});
|
|
expect(normalizeElementIdsForSnapshot(container)).toMatchSnapshot();
|
|
});
|
|
|
|
test('Preview(as Form) pushes lower schemas after list height changes for blank PDFs', async () => {
|
|
setupUIMock();
|
|
const { container } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={formReflowPlugins}>
|
|
<Preview
|
|
template={getFormReflowTemplate({
|
|
width: 100,
|
|
height: 100,
|
|
padding: [10, 10, 10, 10],
|
|
})}
|
|
inputs={[{ tasks: '', footer: 'Footer' }]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
onChangeInput={vi.fn()}
|
|
/>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(container.querySelectorAll('[data-pdfme-render-ready="true"]').length).toBe(2);
|
|
});
|
|
|
|
const footer = container.querySelector('[title="footer"]');
|
|
const topBefore = getTop(footer);
|
|
const growButton = Array.from(container.querySelectorAll('button')).find(
|
|
(button) => button.textContent === 'grow list',
|
|
);
|
|
if (!growButton) throw new Error('Grow list button was not found');
|
|
|
|
fireEvent.click(growButton);
|
|
|
|
await waitFor(() => {
|
|
expect(getTop(footer)).toBeGreaterThan(topBefore);
|
|
});
|
|
});
|
|
|
|
test('Preview(as Form) does not push lower schemas after list height changes for custom PDFs', async () => {
|
|
setupUIMock();
|
|
const { container } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={formReflowPlugins}>
|
|
<Preview
|
|
template={getFormReflowTemplate(CUSTOM_A4_PDF)}
|
|
inputs={[{ tasks: '', footer: 'Footer' }]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
onChangeInput={vi.fn()}
|
|
/>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(container.querySelectorAll('[data-pdfme-render-ready="true"]').length).toBe(2);
|
|
});
|
|
|
|
const footer = container.querySelector('[title="footer"]');
|
|
const topBefore = getTop(footer);
|
|
const growButton = Array.from(container.querySelectorAll('button')).find(
|
|
(button) => button.textContent === 'grow list',
|
|
);
|
|
if (!growButton) throw new Error('Grow list button was not found');
|
|
|
|
fireEvent.click(growButton);
|
|
|
|
await waitFor(() => {
|
|
expect(getTop(footer)).toBe(topBefore);
|
|
});
|
|
});
|
|
|
|
test('Preview keeps toolbar zoom interactive when options.zoomLevel is only an initial value', async () => {
|
|
setupUIMock();
|
|
const { container } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={plugins}>
|
|
<OptionsContext.Provider value={{ zoomLevel: 1 }}>
|
|
<Preview
|
|
template={getSampleTemplate()}
|
|
inputs={[{ field1: 'field1', field2: 'field2' }]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
/>
|
|
</OptionsContext.Provider>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(container.querySelectorAll('[data-pdfme-render-ready="true"]').length).toBeGreaterThan(
|
|
0,
|
|
);
|
|
});
|
|
|
|
expect(container).toHaveTextContent('100%');
|
|
fireEvent.click(container.querySelector('.pdfme-ui-zoom-in')!);
|
|
|
|
await waitFor(() => {
|
|
expect(container).toHaveTextContent('125%');
|
|
});
|
|
});
|
|
|
|
test('Preview does not reapply options.zoomLevel when changing pages', async () => {
|
|
setupUIMock(2);
|
|
const { container } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={plugins}>
|
|
<OptionsContext.Provider value={{ zoomLevel: 1 }}>
|
|
<Preview
|
|
template={getTwoPageTemplate()}
|
|
inputs={[
|
|
{
|
|
field1: 'field1',
|
|
field2: 'field2',
|
|
field1Page2: 'field1Page2',
|
|
field2Page2: 'field2Page2',
|
|
},
|
|
]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
/>
|
|
</OptionsContext.Provider>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(container.querySelectorAll('[data-pdfme-render-ready="true"]').length).toBeGreaterThan(
|
|
0,
|
|
);
|
|
});
|
|
|
|
fireEvent.click(container.querySelector('.pdfme-ui-zoom-in')!);
|
|
await waitFor(() => {
|
|
expect(container).toHaveTextContent('125%');
|
|
});
|
|
|
|
fireEvent.click(container.querySelector('.pdfme-ui-page-next')!);
|
|
await waitFor(() => {
|
|
expect(container).toHaveTextContent('2/2');
|
|
expect(container).toHaveTextContent('125%');
|
|
});
|
|
});
|
|
|
|
test('Preview toolbar fit width updates the zoom level', async () => {
|
|
setupUIMock();
|
|
restoreClientSizeMock = mockClientSizeFromStyle();
|
|
const { container } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={plugins}>
|
|
<Preview
|
|
template={getSampleTemplate()}
|
|
inputs={[{ field1: 'field1', field2: 'field2' }]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
/>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(container.querySelectorAll('[data-pdfme-render-ready="true"]').length).toBeGreaterThan(
|
|
0,
|
|
);
|
|
});
|
|
|
|
fireEvent.click(container.querySelector('.pdfme-ui-fit-width')!);
|
|
|
|
const expectedZoom = Math.round((1160 / (PAGE_SIZE_PRESETS.A4.width * ZOOM)) * 100);
|
|
await waitFor(() => {
|
|
expect(container).toHaveTextContent(`${expectedZoom}%`);
|
|
});
|
|
});
|
|
|
|
test('Preview toolbar fit height returns to 100 percent', async () => {
|
|
setupUIMock();
|
|
restoreClientSizeMock = mockClientSizeFromStyle();
|
|
const { container } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={plugins}>
|
|
<Preview
|
|
template={getSampleTemplate()}
|
|
inputs={[{ field1: 'field1', field2: 'field2' }]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
/>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(container.querySelectorAll('[data-pdfme-render-ready="true"]').length).toBeGreaterThan(
|
|
0,
|
|
);
|
|
});
|
|
|
|
fireEvent.click(container.querySelector('.pdfme-ui-zoom-in')!);
|
|
await waitFor(() => {
|
|
expect(container).toHaveTextContent('125%');
|
|
});
|
|
|
|
fireEvent.click(container.querySelector('.pdfme-ui-fit-height')!);
|
|
await waitFor(() => {
|
|
expect(container).toHaveTextContent('100%');
|
|
});
|
|
});
|
|
|
|
test('Preview zooms with ctrl wheel but not ordinary wheel', async () => {
|
|
setupUIMock();
|
|
restoreClientSizeMock = mockClientSizeFromStyle();
|
|
const { container } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={plugins}>
|
|
<Preview
|
|
template={getSampleTemplate()}
|
|
inputs={[{ field1: 'field1', field2: 'field2' }]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
/>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(container.querySelectorAll('[data-pdfme-render-ready="true"]').length).toBeGreaterThan(
|
|
0,
|
|
);
|
|
});
|
|
|
|
const scrollContainer = getScrollContainer(container);
|
|
fireEvent.wheel(scrollContainer, { deltaY: -100 });
|
|
expect(container).toHaveTextContent('100%');
|
|
|
|
fireEvent.wheel(scrollContainer, { deltaY: -100, ctrlKey: true, clientX: 100, clientY: 100 });
|
|
|
|
await waitFor(() => {
|
|
expect(container).toHaveTextContent('149%');
|
|
});
|
|
});
|
|
|
|
test('Preview zooms with two-finger touch but not one-finger touch', async () => {
|
|
setupUIMock();
|
|
restoreClientSizeMock = mockClientSizeFromStyle();
|
|
const { container } = render(
|
|
<I18nContext.Provider value={i18n}>
|
|
<FontContext.Provider value={getDefaultFont()}>
|
|
<PluginsRegistry.Provider value={plugins}>
|
|
<Preview
|
|
template={getSampleTemplate()}
|
|
inputs={[{ field1: 'field1', field2: 'field2' }]}
|
|
size={{ width: 1200, height: 1200 }}
|
|
/>
|
|
</PluginsRegistry.Provider>
|
|
</FontContext.Provider>
|
|
</I18nContext.Provider>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(container.querySelectorAll('[data-pdfme-render-ready="true"]').length).toBeGreaterThan(
|
|
0,
|
|
);
|
|
});
|
|
|
|
const scrollContainer = getScrollContainer(container);
|
|
dispatchTouchEvent(scrollContainer, 'touchstart', createTouchList([{ clientX: 0, clientY: 0 }]));
|
|
dispatchTouchEvent(scrollContainer, 'touchmove', createTouchList([{ clientX: 0, clientY: 50 }]));
|
|
expect(container).toHaveTextContent('100%');
|
|
|
|
dispatchTouchEvent(
|
|
scrollContainer,
|
|
'touchstart',
|
|
createTouchList([
|
|
{ clientX: 0, clientY: 0 },
|
|
{ clientX: 100, clientY: 0 },
|
|
]),
|
|
);
|
|
dispatchTouchEvent(
|
|
scrollContainer,
|
|
'touchmove',
|
|
createTouchList([
|
|
{ clientX: 0, clientY: 0 },
|
|
{ clientX: 125, clientY: 0 },
|
|
]),
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(container).toHaveTextContent('149%');
|
|
});
|
|
});
|