Skip to content

Commit

Permalink
Convert to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
wojtekmaj committed Apr 17, 2023
1 parent 501a286 commit 64e7b77
Show file tree
Hide file tree
Showing 42 changed files with 926 additions and 256 deletions.
6 changes: 4 additions & 2 deletions __mocks__/_failing_page.js → __mocks__/_failing_page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { PDFPageProxy } from 'pdfjs-dist';

export default {
cleanup: () => {
// Intentionally empty
return true;
},
commonObjs: {
get: () => {
Expand All @@ -21,4 +23,4 @@ export default {
// Intentionally empty
},
}),
};
} as unknown as PDFPageProxy;
4 changes: 3 additions & 1 deletion __mocks__/_failing_pdf.js → __mocks__/_failing_pdf.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { PDFDocumentProxy } from 'pdfjs-dist';

export default {
_pdfInfo: {
fingerprint: 'a62067476e69734bb8eb60122615dfbf',
Expand All @@ -7,4 +9,4 @@ export default {
getOutline: () => new Promise((resolve, reject) => reject(new Error())),
getPage: () => new Promise((resolve, reject) => reject(new Error())),
numPages: 4,
};
} as unknown as PDFDocumentProxy;
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { RenderingCancelledException } from 'pdfjs-dist';

import type { PDFDocumentProxy } from 'pdfjs-dist';

export default {
_pdfInfo: {
fingerprint: 'a62067476e69734bb8eb60122615dfbf',
Expand All @@ -18,4 +20,4 @@ export default {
reject(new RenderingCancelledException('Cancelled', 'cancelled')),
),
numPages: 4,
};
} as unknown as PDFDocumentProxy;
62 changes: 49 additions & 13 deletions src/Document.spec.jsx → src/Document.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,26 @@ import Page from './Page';

import { makeAsyncCallback, loadPDF, muteConsole, restoreConsole } from '../test-utils';

import type { PDFDocumentProxy } from 'pdfjs-dist';
import type { ScrollPageIntoViewArgs } from './shared/types';

const pdfFile = loadPDF('./__mocks__/_pdf.pdf');
const pdfFile2 = loadPDF('./__mocks__/_pdf2.pdf');

const OK = Symbol('OK');

// eslint-disable-next-line react/prop-types
function ChildInternal({ renderMode, rotate }) {
function ChildInternal({
renderMode,
rotate,
}: {
renderMode?: string | null;
rotate?: number | null;
}) {
return <div data-testid="child" data-rendermode={renderMode} data-rotate={rotate} />;
}

function Child(props) {
function Child(props: React.ComponentProps<typeof ChildInternal>) {
return (
<DocumentContext.Consumer>
{(context) => <ChildInternal {...context} {...props} />}
Expand All @@ -36,8 +45,8 @@ async function waitForAsync() {

describe('Document', () => {
// Object with basic loaded PDF information that shall match after successful loading
const desiredLoadedPdf = {};
const desiredLoadedPdf2 = {};
const desiredLoadedPdf: Partial<PDFDocumentProxy> = {};
const desiredLoadedPdf2: Partial<PDFDocumentProxy> = {};

beforeAll(async () => {
const pdf = await pdfjs.getDocument({ data: pdfFile.arrayBuffer }).promise;
Expand Down Expand Up @@ -144,6 +153,7 @@ describe('Document', () => {

muteConsole();

// @ts-expect-error-next-line
render(<Document file={() => null} onSourceError={onSourceError} />);

expect.assertions(1);
Expand Down Expand Up @@ -200,7 +210,7 @@ describe('Document', () => {
});

it('passes container element to inputRef properly', () => {
const inputRef = createRef();
const inputRef = createRef<HTMLDivElement>();

render(<Document inputRef={inputRef} />);

Expand Down Expand Up @@ -423,7 +433,10 @@ describe('Document', () => {
const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback();

const onItemClick = vi.fn();
const instance = createRef();
const instance = createRef<{
pages: React.RefObject<Record<string, unknown>[]>;
viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>;
}>();

render(
<Document
Expand All @@ -434,11 +447,19 @@ describe('Document', () => {
/>,
);

if (!instance.current) {
throw new Error('Document ref is not set');
}

if (!instance.current.viewer.current) {
throw new Error('Viewer ref is not set');
}

expect.assertions(2);

await onLoadSuccessPromise;

const dest = [];
const dest: number[] = [];
const pageIndex = 5;
const pageNumber = 6;

Expand All @@ -451,17 +472,32 @@ describe('Document', () => {

it('attempts to find a page and scroll it into view if onItemClick is not given', async () => {
const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback();
const instance = createRef();
const instance = createRef<{
pages: React.RefObject<Record<string, unknown>[]>;
viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>;
}>();

render(<Document file={pdfFile.file} onLoadSuccess={onLoadSuccess} ref={instance} />);

if (!instance.current) {
throw new Error('Document ref is not set');
}

if (!instance.current.pages.current) {
throw new Error('Pages ref is not set');
}

if (!instance.current.viewer.current) {
throw new Error('Viewer ref is not set');
}

expect.assertions(1);

await onLoadSuccessPromise;

const scrollIntoView = vi.fn();

const dest = [];
const dest: number[] = [];
const pageIndex = 5;
const pageNumber = 6;

Expand Down Expand Up @@ -505,7 +541,7 @@ describe('Document', () => {

await onRenderAnnotationLayerSuccessPromise;

const link = container.querySelector('a');
const link = container.querySelector('a') as HTMLAnchorElement;

expect(link.target).toBe(target);
},
Expand Down Expand Up @@ -540,7 +576,7 @@ describe('Document', () => {

await onRenderAnnotationLayerSuccessPromise;

const link = container.querySelector('a');
const link = container.querySelector('a') as HTMLAnchorElement;

expect(link.rel).toBe(rel);
},
Expand All @@ -551,7 +587,7 @@ describe('Document', () => {

const { container } = render(<Document onClick={onClick} />);

const document = container.querySelector('.react-pdf__Document');
const document = container.querySelector('.react-pdf__Document') as HTMLDivElement;
fireEvent.click(document);

expect(onClick).toHaveBeenCalled();
Expand All @@ -562,7 +598,7 @@ describe('Document', () => {

const { container } = render(<Document onTouchStart={onTouchStart} />);

const document = container.querySelector('.react-pdf__Document');
const document = container.querySelector('.react-pdf__Document') as HTMLDivElement;
fireEvent.touchStart(document);

expect(onTouchStart).toHaveBeenCalled();
Expand Down
104 changes: 92 additions & 12 deletions src/Document.jsx → src/Document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,67 @@ import {
import { useResolver } from './shared/hooks';
import { eventProps, isClassName, isFile, isRef } from './shared/propTypes';

import type { PDFDocumentProxy } from 'pdfjs-dist';
import type {
ClassName,
ExternalLinkRel,
ExternalLinkTarget,
File,
ImageResourcesPath,
NodeOrRenderer,
OnError,
OnItemClickArgs,
OnLoadProgressArgs,
OnPasswordCallback,
Options,
PasswordResponse,
RenderMode,
ScrollPageIntoViewArgs,
Source,
} from './shared/types';

const { PDFDataRangeTransport } = pdfjs;

const defaultOnPassword = (callback, reason) => {
type OnItemClick = (args: OnItemClickArgs) => void;

type OnLoadError = OnError;

type OnLoadProgress = (args: OnLoadProgressArgs) => void;

type OnLoadSuccess = (pdf: PDFDocumentProxy) => void;

type OnPassword = (callback: OnPasswordCallback, reason: PasswordResponse) => void;

type OnSourceError = OnError;

type OnSourceSuccess = () => void;

type EventProps = ReturnType<typeof makeEventProps>;

type DocumentProps = {
children?: React.ReactNode;
className?: ClassName;
error?: NodeOrRenderer;
externalLinkRel?: ExternalLinkRel;
externalLinkTarget?: ExternalLinkTarget;
file?: File;
imageResourcesPath?: ImageResourcesPath;
inputRef?: React.Ref<HTMLDivElement>;
loading?: NodeOrRenderer;
noData?: NodeOrRenderer;
onItemClick?: OnItemClick;
onLoadError?: OnLoadError;
onLoadProgress?: OnLoadProgress;
onLoadSuccess?: OnLoadSuccess;
onPassword?: OnPassword;
onSourceError?: OnSourceError;
onSourceSuccess?: OnSourceSuccess;
options?: Options;
renderMode?: RenderMode;
rotate?: number | null;
} & EventProps;

const defaultOnPassword: OnPassword = (callback, reason) => {
switch (reason) {
case PasswordResponses.NEED_PASSWORD: {
// eslint-disable-next-line no-alert
Expand Down Expand Up @@ -81,21 +139,21 @@ const Document = forwardRef(function Document(
renderMode,
rotate,
...otherProps
},
}: DocumentProps,
ref,
) {
const [sourceState, sourceDispatch] = useResolver();
const [sourceState, sourceDispatch] = useResolver<Source | null>();
const { value: source, error: sourceError } = sourceState;
const [pdfState, pdfDispatch] = useResolver();
const [pdfState, pdfDispatch] = useResolver<PDFDocumentProxy>();
const { value: pdf, error: pdfError } = pdfState;

const linkService = useRef(new LinkService());

const pages = useRef([]);
const pages = useRef<HTMLDivElement[]>([]);

const viewer = useRef({
// Handling jumping to internal links target
scrollPageIntoView: ({ dest, pageIndex, pageNumber }) => {
scrollPageIntoView: ({ dest, pageIndex, pageNumber }: ScrollPageIntoViewArgs) => {
// First, check if custom handling of onItemClick was provided
if (onItemClick) {
onItemClick({ dest, pageIndex, pageNumber });
Expand Down Expand Up @@ -143,7 +201,12 @@ const Document = forwardRef(function Document(
* Called when a document source failed to be resolved correctly
*/
function onSourceError() {
warning(false, sourceError);
if (!sourceError) {
// Impossible, but TypeScript doesn't know that
return;
}

warning(false, sourceError.toString());

if (onSourceErrorProps) {
onSourceErrorProps(sourceError);
Expand All @@ -156,7 +219,7 @@ const Document = forwardRef(function Document(

useEffect(resetSource, [file, sourceDispatch]);

const findDocumentSource = useCallback(async () => {
const findDocumentSource = useCallback(async (): Promise<Source | null> => {
if (!file) {
return null;
}
Expand Down Expand Up @@ -258,6 +321,11 @@ const Document = forwardRef(function Document(
* Called when a document is read successfully
*/
function onLoadSuccess() {
if (!pdf) {
// Impossible, but TypeScript doesn't know that
return;
}

if (onLoadSuccessProps) {
onLoadSuccessProps(pdf);
}
Expand All @@ -270,7 +338,12 @@ const Document = forwardRef(function Document(
* Called when a document failed to read successfully
*/
function onLoadError() {
warning(false, pdfError);
if (!pdfError) {
// Impossible, but TypeScript doesn't know that
return;
}

warning(false, pdfError.toString());

if (onLoadErrorProps) {
onLoadErrorProps(pdfError);
Expand All @@ -288,7 +361,14 @@ const Document = forwardRef(function Document(
return;
}

const destroyable = pdfjs.getDocument({ ...source, ...options });
const documentInitParams = options
? {
...source,
...options,
}
: source;

const destroyable = pdfjs.getDocument(documentInitParams);
if (onLoadProgress) {
destroyable.onProgress = onLoadProgress;
}
Expand Down Expand Up @@ -343,11 +423,11 @@ const Document = forwardRef(function Document(

useEffect(setupLinkService, [externalLinkRel, externalLinkTarget]);

function registerPage(pageIndex, ref) {
function registerPage(pageIndex: number, ref: HTMLDivElement) {
pages.current[pageIndex] = ref;
}

function unregisterPage(pageIndex) {
function unregisterPage(pageIndex: number) {
delete pages.current[pageIndex];
}

Expand Down
3 changes: 0 additions & 3 deletions src/DocumentContext.jsx

This file was deleted.

5 changes: 5 additions & 0 deletions src/DocumentContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createContext } from 'react';

import type { DocumentContextType } from './shared/types';

export default createContext<DocumentContextType>(null);
Loading

0 comments on commit 64e7b77

Please sign in to comment.