Skip to content

Commit

Permalink
Merge pull request #34 from KoreanThinker/throttle
Browse files Browse the repository at this point in the history
apply throttle
  • Loading branch information
KoreanThinker authored May 1, 2024
2 parents 15b0177 + ccc82f5 commit e716c6d
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 24 deletions.
2 changes: 1 addition & 1 deletion example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const Component = () => {
from="en"
to="ko"
value={value}
type="papago"
type="google"
onTranslated={t => setResult(t)}
/>
<TextInput
Expand Down
5 changes: 5 additions & 0 deletions jest-setup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import '@testing-library/react-native/extend-expect';
import {jest} from '@jest/globals';
import _ from 'lodash';

// Mocking native module (react-native-webview)
// native module not supported in @testing-library/react-native
Expand Down Expand Up @@ -32,3 +33,7 @@ jest.mock('react-native-webview', () => {
default: TestWebView,
};
});

// ignore debounce on tests
// @ts-ignore
jest.spyOn(_, 'debounce').mockImplementation(fn => fn);
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@testing-library/react-native": "^12.4.5",
"@tsconfig/react-native": "^3.0.5",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.0",
"@types/react": "^18.3.1",
"eslint": "^8.19.0",
"eslint-plugin-testing-library": "^6.2.2",
Expand All @@ -58,5 +59,8 @@
"react-native-webview": "^11.26.1",
"react-test-renderer": "^18.3.1",
"typescript": "5.1.5"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
36 changes: 24 additions & 12 deletions src/components/Translator.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useCallback, useMemo} from 'react';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import _ from 'lodash';
import WebView, {WebViewMessageEvent} from 'react-native-webview';
import {View} from 'react-native';
import {LanguageCode, SourceLanguageCode, TranslatorType} from '..';
import translators from '../translators';
import {LOADING_MESSSAGE} from '../classes/translator';
Expand All @@ -16,7 +16,8 @@ export interface TranslatorProps<T extends TranslatorType> {
function Translator<T extends TranslatorType = 'google'>(
props: TranslatorProps<T>,
) {
const {from, to, value, onTranslated, type = 'google'} = props;
const {from, to, value: _value, onTranslated, type = 'google'} = props;
const [value, setValue] = useState('');
const translator = useMemo(() => translators[type], [type]);
const injectedJavascript = useMemo(
() => translator.getInjectedJavascript(),
Expand All @@ -36,16 +37,27 @@ function Translator<T extends TranslatorType = 'google'>(
onTranslated(result);
}, []);

// set value throttled
const throttledSetValue = useMemo(() => _.debounce(setValue, 100), []);
useEffect(() => {
if (_value === '') {
// clear value when input value is empty
setValue('');
onTranslated('');
return;
}
throttledSetValue(_value);
}, [_value]);

return (
<View style={{display: 'none'}}>
<WebView
injectedJavaScript={injectedJavascript}
userAgent={userAgent}
source={{uri}}
onMessage={onMessage}
cacheEnabled
/>
</View>
<WebView
style={{width: 0, height: 0}}
injectedJavaScript={injectedJavascript}
userAgent={userAgent}
source={{uri}}
onMessage={onMessage}
cacheEnabled
/>
);
}

Expand Down
12 changes: 12 additions & 0 deletions src/components/__test__/Translator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,15 @@ test('Render with empty from and to', () => {

expect(screen.queryByText(/http/)).not.toBeOnTheScreen();
});

test('Render value change to empty', () => {
const onTranslated = jest.fn();
const {rerender} = render(
<Translator from="en" to="ko" value="hello" onTranslated={() => {}} />,
);
rerender(
<Translator from="en" to="ko" value="" onTranslated={onTranslated} />,
);

expect(onTranslated).toHaveBeenCalledWith('');
});
11 changes: 8 additions & 3 deletions src/contexts/TranslatorContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export function TranslatorProvider({children}: {children: React.ReactNode}) {

const translate: Translate = useCallback(
async (_from, _to, _value, option) => {
if (_value === '') {
return '';
}
return new Promise<string>((resolve, reject) => {
setTasks(prev => {
const timeout = option?.timeout ?? 5000;
Expand Down Expand Up @@ -75,9 +78,11 @@ export function TranslatorProvider({children}: {children: React.ReactNode}) {

return (
<TranslatorContext.Provider value={{translate}}>
{tasks.map((task, index) => (
<TranslatorWrapper {...task} key={index} />
))}
<View style={{width: 0, height: 0}}>
{tasks.map((task, index) => (
<TranslatorWrapper {...task} key={index} />
))}
</View>
{children}
</TranslatorContext.Provider>
);
Expand Down
33 changes: 25 additions & 8 deletions src/contexts/__test__/TranslatorContext.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test('translate once', async () => {
act(() => {
promise = translate('en', 'ko', 'hello', {type: 'kakao'});
});

// throttle 100ms
const expectedUri = 'https://translate.kakao.com/?lang=enko&q=hello';
expect(screen.getByText(expectedUri)).toBeOnTheScreen();
const expectedUserAgent = /Mozilla/;
Expand All @@ -23,7 +23,9 @@ test('translate once', async () => {
expect(screen.getByText(/#result/)).toBeOnTheScreen();

// mock webview always response after 1000ms. refer to jest-setup.tsx
jest.advanceTimersByTime(1000);
act(() => {
jest.advanceTimersByTime(1000);
});
// @ts-ignore - Variable 'promise' is used before being assigned.ts(2454)
await expect(promise).resolves.toBe('안녕');
});
Expand All @@ -48,8 +50,9 @@ test('translate multiple', async () => {
expect(screen.getByText(expectedUri2)).toBeOnTheScreen();

// mock webview always response after 1000ms. refer to jest-setup.tsx
jest.advanceTimersByTime(1000);

act(() => {
jest.advanceTimersByTime(1000);
});
// @ts-ignore - Variable 'promise' is used before being assigned.ts(2454)
await expect(promise1).resolves.toBe('안녕');
// @ts-ignore - Variable 'promise' is used before being assigned.ts(2454)
Expand All @@ -69,8 +72,9 @@ test('translate multiple', async () => {
expect(screen.getByText(expectedUri3)).toBeOnTheScreen();

// mock webview always response after 1000ms. refer to jest-setup.tsx
jest.advanceTimersByTime(1000);

act(() => {
jest.advanceTimersByTime(1000);
});
// @ts-ignore - Variable 'promise' is used before being assigned.ts(2454)
await expect(promise3).resolves.toBe('안녕');
// inactive after translated
Expand Down Expand Up @@ -106,8 +110,21 @@ test('translate timeout', async () => {
// @ts-ignore - Variable 'promise' is used before being assigned.ts(2454)
promise = translate('en', 'ko', 'hello', {timeout: 500});
});

jest.advanceTimersByTime(500);
act(() => {
jest.advanceTimersByTime(500);
});
// @ts-ignore - Variable 'promise' is used before being assigned.ts(2454)
await expect(promise).rejects.toBe('translate timeout');
});

test('translate nothing when empty value', async () => {
const hook = renderHook(useTranslator, {
wrapper: TranslatorProvider,
});

const {translate} = hook.result.current;

await expect(translate('en', 'ko', '')).resolves.toBe('');

expect(screen.queryByText(/http/)).not.toBeOnTheScreen();
});
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2000,6 +2000,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==

"@types/lodash@^4.17.0":
version "4.17.0"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3"
integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==

"@types/node-forge@^1.3.0":
version "1.3.11"
resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da"
Expand Down

0 comments on commit e716c6d

Please sign in to comment.