Skip to content

Commit

Permalink
chore(lint): activated error level on exhaustive-deps
Browse files Browse the repository at this point in the history
  • Loading branch information
trajano committed Nov 28, 2024
1 parent 95eec9e commit 7883559
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 47 deletions.
1 change: 1 addition & 0 deletions packages/eslint-config-my/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const rules: Linter.RulesRecord = {
},
],
eqeqeq: ['error'],
'react-hooks/exhaustive-deps': ['error'],
'import/no-unresolved': ['error'],
'no-restricted-imports': [
'error',
Expand Down
2 changes: 1 addition & 1 deletion packages/my-app/src/app/secure/my-resume-pipeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const ResumeScreen: FC = () => {
}
const localUri = (await Asset.fromURI(randomUri).downloadAsync()).localUri!;
postPdfRequest('a', localUri);
}, [loadedUri]);
}, [loadedUri, postPdfRequest]);
const router = useRouter();
console.debug({ uri, pageCount, dimensions });
return (
Expand Down
17 changes: 10 additions & 7 deletions packages/my-app/src/app/secure/webview-communications.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { FC, RefObject, useCallback, useRef, useState } from 'react';
import {
FC,
RefObject,
useCallback,
useReducer,
useRef,
useState,
} from 'react';
import { Button, View } from 'react-native';
import { MyText, MyTextInput } from 'react-native-my-text';
import {
Expand All @@ -12,14 +19,13 @@ import * as FileSystem from 'expo-file-system';
const WebViewCommunicationForm: FC<{
messagesRef: RefObject<string[]>;
}> = ({ messagesRef }) => {
const [messages, setMessages] = useState<string[]>([]);
const [messages, refreshMessages] = useReducer(() => messagesRef.current, []);
const { IpcWebView, postMessage } = useIpcWeb();
const [input, setInput] = useState('test message');
const onChangeText = useCallback((nextInput: string) => {
setInput(nextInput);
}, []);
const onSendMessage = useCallback(() => {
console.log('onSend');
postMessage({ input });
}, [postMessage, input]);
const onSendUriMessage = useCallback(() => {
Expand All @@ -31,9 +37,6 @@ const WebViewCommunicationForm: FC<{
postMessage({ input: fileUri, isUri: true });
})();
}, [postMessage, input]);
const onRefresh = useCallback(() => {
setMessages(messagesRef.current ?? []);
}, [postMessage, input]);

return (
<View style={{ backgroundColor: 'silver' }}>
Expand All @@ -44,7 +47,7 @@ const WebViewCommunicationForm: FC<{
<MyText></MyText>
<Button title="Send URI message to webview" onPress={onSendUriMessage} />
<MyText></MyText>
<Button title="Refresh" onPress={onRefresh} />
<Button title="Refresh" onPress={refreshMessages} />
<MyText>{JSON.stringify(messages, null, 2)}</MyText>
</View>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/my-app/src/hooks/useViewDimensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const useViewDimensions = (
height: (windowWidth / width) * height,
};
}
}, [width, height]);
}, [initialHeight, width, height]);
return {
setHeight,
setWidth,
Expand Down
21 changes: 12 additions & 9 deletions packages/react-native-ipc-web/src/IpcWeb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,25 @@ export const IpcWebProvider = <T extends object>({
},
[onMessage],
);
let resolveIpcWebViewReady: (value: boolean) => void;
let rejectIpcWebViewReady: (error: unknown) => void;
const resolveIpcWebViewReadyRef = useRef<(value: boolean) => void>();
const rejectIpcWebViewReadyRef = useRef<(error: unknown) => void>();

const ipcWebViewReadyPromise = useRef(
new Promise<boolean>((resolve, reject) => {
resolveIpcWebViewReady = resolve; // Store the resolve function
rejectIpcWebViewReady = reject;
resolveIpcWebViewReadyRef.current = resolve; // Store the resolve function
rejectIpcWebViewReadyRef.current = reject;
}),
).current;
const ipcOnLoad = useCallback(() => {
resolveIpcWebViewReady(true);
resolveIpcWebViewReadyRef.current &&
resolveIpcWebViewReadyRef.current(true);
}, []);
const ipcOnError = useCallback((event: WebViewErrorEvent) => {
rejectIpcWebViewReady(
new Error(`Webview Error: ${event.nativeEvent.code}`),
);
if (rejectIpcWebViewReadyRef.current) {
rejectIpcWebViewReadyRef.current(
new Error(`Webview Error: ${event.nativeEvent.code}`),
);
}
}, []);
const ContextIpcWebView = useMemo<FC<Record<string, never>>>(() => {
// The random UUID is needed to allow reloads
Expand All @@ -118,7 +121,7 @@ export const IpcWebProvider = <T extends object>({
ref={ipcWebViewRef}
/>
);
}, [sourceProvider, ipcOnMessage]);
}, [sourceProvider, ipcOnMessage, ipcOnLoad, ipcOnError]);
ContextIpcWebView.displayName = 'IpcWebView';

const postMessage = useCallback((message: object) => {
Expand Down
188 changes: 188 additions & 0 deletions packages/react-native-pdf-view/src/PdfPipelineView.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { render, act, screen } from '@testing-library/react-native';
import { PdfPipelineView } from './PdfPipelineView';
import { usePdfPipeline } from './PdfPipeline';
import * as Crypto from 'expo-crypto';
import { Asset } from 'expo-asset';
import { PdfPipelineMessage } from './PdfPipelineMessage';

// Mock dependencies
jest.mock('./PdfPipeline', () => ({
usePdfPipeline: jest.fn(),
}));

jest.mock('expo-crypto', () => ({
randomUUID: jest.fn(),
}));

jest.mock('expo-asset', () => ({
Asset: {
fromURI: jest.fn(),
},
}));

describe('PdfPipelineView', () => {
const mockPostPdfRequest = jest.fn();
const mockAddListener = jest.fn();
const mockPdfPipelineReadyPromise = Promise.resolve(true);

beforeEach(() => {
jest.clearAllMocks();
(Crypto.randomUUID as jest.Mock).mockReturnValue('test-correlation-id');
(usePdfPipeline as jest.Mock).mockReturnValue({
postPdfRequest: mockPostPdfRequest,
addListener: mockAddListener,
pdfPipelineReadyPromise: mockPdfPipelineReadyPromise,
});
(Asset.fromURI as jest.Mock).mockReturnValue({
downloadAsync: jest
.fn()
.mockResolvedValue({ localUri: 'file://test.pdf' }),
});
});

it('renders without crashing', () => {
const { getByTestId } = render(
<PdfPipelineView
uri="test-uri"
pageNumber={1}
scale={1.0}
onViewPortKnown={jest.fn()}
onError={jest.fn()}
onPageCountKnown={jest.fn()}
contentFit="cover"
style={{ width: 100, height: 100 }}
testID="pdf-pipeline-view"
/>,
);

expect(getByTestId('pdf-pipeline-view')).toBeTruthy();
});

it('sets up event listeners and posts PDF request', async () => {
const { rerender } = render(
<PdfPipelineView
uri="test-uri"
pageNumber={1}
scale={1.0}
onViewPortKnown={jest.fn()}
onError={jest.fn()}
onPageCountKnown={jest.fn()}
testID="pdf-pipeline-view"
/>,
);

// Assert listeners and postPdfRequest were called
await act(async () => {
expect(mockAddListener).toHaveBeenCalledTimes(5);
});
await act(async () => {
expect(mockPostPdfRequest).toHaveBeenCalledWith(
'test-correlation-id',
'file://test.pdf',
1,
1.0,
);
});

expect(screen.getByTestId('pdf-pipeline-view')).toBeTruthy();
expect(screen.getByTestId('pdf-pipeline-view').props.source).toEqual([]);

const okCallback = mockAddListener.mock.calls.find(
(it) => it[0] === 'ok',
)[2];
expect(typeof okCallback).toBe('function');

await act(async () => {
okCallback({
type: 'ok',
data: 'abc',
correlationId: 'test-correlation-id',
} satisfies PdfPipelineMessage);
});
expect(screen.getByTestId('pdf-pipeline-view')).toBeTruthy();
expect(screen.getByTestId('pdf-pipeline-view').props.source).toContainEqual(
{ uri: 'abc' },
);

expect(screen.toJSON()).toMatchSnapshot();

mockAddListener.mockClear();

// Update props and ensure effect runs again
rerender(
<PdfPipelineView
uri="new-test-uri"
pageNumber={2}
scale={1.5}
onViewPortKnown={jest.fn()}
onError={jest.fn()}
onPageCountKnown={jest.fn()}
testID="pdf-pipeline-view"
/>,
);
await act(async () => Promise.resolve());
await act(async () => {
expect(mockAddListener).toHaveBeenCalledTimes(5);
expect(mockPostPdfRequest).toHaveBeenCalledWith(
'test-correlation-id',
'file://test.pdf',
2,
1.5,
);
});
expect(screen.getByTestId('pdf-pipeline-view')).toBeTruthy();
expect(screen.getByTestId('pdf-pipeline-view').props.source).toContainEqual(
{ uri: 'abc' },
);

const receivedCallback = mockAddListener.mock.calls.find(
(it) => it[0] === 'received',
)[2];
expect(typeof okCallback).toBe('function');
await act(async () => {
receivedCallback({
type: 'received',
uri: 'new-test-uri',
pageNumber: 2,
scale: 1.5,
correlationId: 'test-correlation-id',
} satisfies PdfPipelineMessage);
});
expect(screen.getByTestId('pdf-pipeline-view').props.source).toEqual([]);

const rerenderOkCallback = mockAddListener.mock.calls.find(
(it) => it[0] === 'ok',
)[2];

await act(async () => {
rerenderOkCallback({
type: 'ok',
data: 'new-abc',
correlationId: 'test-correlation-id',
} satisfies PdfPipelineMessage);
});
expect(screen.getByTestId('pdf-pipeline-view').props.source).toContainEqual(
{ uri: 'new-abc' },
);
expect(screen.toJSON()).toMatchSnapshot();
});

it('cleans up listeners on unmount', () => {
const mockRemoveListener = jest.fn();
mockAddListener.mockReturnValue({ remove: mockRemoveListener });

const { unmount } = render(
<PdfPipelineView
uri="test-uri"
pageNumber={1}
scale={1.0}
onViewPortKnown={jest.fn()}
onError={jest.fn()}
onPageCountKnown={jest.fn()}
/>,
);

unmount();
expect(mockRemoveListener).toHaveBeenCalledTimes(5);
});
});
Loading

0 comments on commit 7883559

Please sign in to comment.