From 8f42db5550fdbf688d288ca90f0ab1d01aabbba8 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 19 Nov 2024 17:00:17 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=ED=83=80=EC=9D=B4=EB=A8=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20worker=EC=97=90=20=EC=9C=84?= =?UTF-8?q?=EC=9E=84=20#403?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SelectContainer/Timer/hooks/timerWorker.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 frontend/src/pages/GamePage/components/SelectContainer/Timer/hooks/timerWorker.ts diff --git a/frontend/src/pages/GamePage/components/SelectContainer/Timer/hooks/timerWorker.ts b/frontend/src/pages/GamePage/components/SelectContainer/Timer/hooks/timerWorker.ts new file mode 100644 index 000000000..abdb37182 --- /dev/null +++ b/frontend/src/pages/GamePage/components/SelectContainer/Timer/hooks/timerWorker.ts @@ -0,0 +1,17 @@ +let intervalId: NodeJS.Timeout; + +self.onmessage = function (e) { + const { type, delay } = e.data; + + if (type === 'start') { + const startTime = Date.now(); + + intervalId = setInterval(() => { + const elapsedTime = Date.now() - startTime; + + self.postMessage({ elapsedTime }); + }, delay); + } else if (type === 'stop') { + clearInterval(intervalId); + } +}; From 6f35e61f70d8fda8b1333ee7b17850231d140472 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 19 Nov 2024 17:07:44 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20worker=EB=A5=BC=20=ED=83=80?= =?UTF-8?q?=EC=9D=B4=EB=A8=B8=EC=97=90=20=EC=A0=81=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=ED=83=AD=20=EC=A0=84=ED=99=98=EC=8B=9C=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=98=EB=8A=94=20=ED=83=80=EC=9D=B4=EB=A8=B8=20?= =?UTF-8?q?=EC=98=A4=EC=B0=A8=20=EA=B0=9C=EC=84=A0=20#403?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SelectContainer/Timer/hooks/useTimer.ts | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/frontend/src/pages/GamePage/components/SelectContainer/Timer/hooks/useTimer.ts b/frontend/src/pages/GamePage/components/SelectContainer/Timer/hooks/useTimer.ts index 3d0a4cda1..8760b7ec8 100644 --- a/frontend/src/pages/GamePage/components/SelectContainer/Timer/hooks/useTimer.ts +++ b/frontend/src/pages/GamePage/components/SelectContainer/Timer/hooks/useTimer.ts @@ -11,11 +11,27 @@ interface UseTimerProps { const useTimer = ({ timeLimit, isSelectedOption, isVoted, vote }: UseTimerProps) => { const [leftRoundTime, setLeftRoundTime] = useState(timeLimit); + const workerRef = useRef(null); const isVoteTimeout = leftRoundTime <= 0; const isAlmostFinished = leftRoundTime <= ALMOST_FINISH_SECOND; - const timeout = useRef(); + useEffect(() => { + const timerWorker = new Worker(new URL('./timerWorker.ts', import.meta.url)); + workerRef.current = timerWorker; + + timerWorker.postMessage({ type: 'start', delay: POLLING_DELAY }); + + timerWorker.onmessage = () => { + setLeftRoundTime((prev) => prev - 1); + }; + + // 타이머가 끝나기 전에 투표가 완료될 경우 clean-up + return () => { + timerWorker.postMessage({ type: 'stop' }); + timerWorker.terminate(); + }; + }, []); useEffect(() => { if (isVoteTimeout) { @@ -23,20 +39,11 @@ const useTimer = ({ timeLimit, isSelectedOption, isVoted, vote }: UseTimerProps) vote(); } - clearInterval(timeout.current); + workerRef.current?.postMessage({ type: 'stop' }); + workerRef.current?.terminate(); } }, [isVoteTimeout, isSelectedOption, isVoted, vote]); - useEffect(() => { - timeout.current = setInterval(() => { - setLeftRoundTime((prev) => prev - 1); - }, POLLING_DELAY); - - return () => { - clearInterval(timeout.current); - }; - }, [timeLimit]); - return { leftRoundTime, isAlmostFinished }; }; From 47229a57e777f4d13c64b56270240bc74251948e Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 20 Nov 2024 02:43:52 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20web=20worker=20=EB=AA=A8?= =?UTF-8?q?=ED=82=B9=20=EC=B6=94=EA=B0=80=20#403?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/jest.config.json | 2 +- frontend/src/mocks/worker.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 frontend/src/mocks/worker.js diff --git a/frontend/jest.config.json b/frontend/jest.config.json index 404f38d60..c0bd17adf 100644 --- a/frontend/jest.config.json +++ b/frontend/jest.config.json @@ -12,7 +12,7 @@ "\\.[jt]sx?$": "@swc/jest", "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/src/mocks/fileMock.js" }, - "setupFiles": ["./jest.polyfills.js"], + "setupFiles": ["./jest.polyfills.js", "./src/mocks/worker.js"], "setupFilesAfterEnv": ["/jest.setup.ts"], "clearMocks": true } diff --git a/frontend/src/mocks/worker.js b/frontend/src/mocks/worker.js new file mode 100644 index 000000000..e69989bf9 --- /dev/null +++ b/frontend/src/mocks/worker.js @@ -0,0 +1,15 @@ +export default class TimerWorker { + constructor(stringUrl) { + this.url = stringUrl; + this.onmessage = () => {}; + } + + postMessage(message) { + this.onmessage(message); + } + + terminate() {} +} + +// Jest 환경에서 Web Worker를 Mock으로 대체 +window.Worker = TimerWorker; From afd961d42a2b99d5e3875100629ebbd80f9db680 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 20 Nov 2024 02:43:58 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20=EB=A9=94=EC=9D=B8=20=EC=8A=A4?= =?UTF-8?q?=EB=A0=88=EB=93=9C=EC=99=80=20=EB=8F=85=EB=A6=BD=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20worker=20thread=EC=9D=98=20=ED=83=80=EC=9D=B4?= =?UTF-8?q?=EB=A8=B8=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=95=A0=20=EC=88=98=20=EC=97=86=EC=96=B4=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=EB=A8=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20#403?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SelectContainer/Timer/Timer.test.tsx | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/frontend/src/pages/GamePage/components/SelectContainer/Timer/Timer.test.tsx b/frontend/src/pages/GamePage/components/SelectContainer/Timer/Timer.test.tsx index 2927d59d8..a3c634662 100644 --- a/frontend/src/pages/GamePage/components/SelectContainer/Timer/Timer.test.tsx +++ b/frontend/src/pages/GamePage/components/SelectContainer/Timer/Timer.test.tsx @@ -1,6 +1,3 @@ -import { act, renderHook } from '@testing-library/react'; - -import useTimer from './hooks/useTimer'; import { convertMsecToSecond, formatLeftRoundTime } from './Timer.util'; describe('Timer 테스트', () => { @@ -17,56 +14,4 @@ describe('Timer 테스트', () => { expect(convertedTime).toBe(10); }); }); - describe('Timer 훅 테스트', () => { - jest.useFakeTimers(); - const voteMock = jest.fn(); - const timeLimit = 10; - const timeLimitMs = timeLimit * 1000; - - it('타이머가 종료되었을 때 선택 완료를 누르지 않아도 선택된 옵션이 있으면 투표한다.', () => { - const isSelectedOption = true; - const isVoted = false; - - const { result } = renderHook(() => - useTimer({ timeLimit, isSelectedOption, isVoted, vote: voteMock }), - ); - - act(() => { - jest.advanceTimersByTime(timeLimitMs); - }); - - expect(result.current.leftRoundTime).toBe(0); - expect(voteMock).toHaveBeenCalled(); - }); - it('타이머가 종료되었을 때 이미 투표를 했다면 또 투표를 하지 않는다.', () => { - const isSelectedOption = true; - const isVoted = true; - - const { result } = renderHook(() => - useTimer({ timeLimit, isSelectedOption, isVoted, vote: voteMock }), - ); - - act(() => { - jest.advanceTimersByTime(timeLimitMs); - }); - - expect(result.current.leftRoundTime).toBe(0); - expect(voteMock).not.toHaveBeenCalled(); - }); - it('타이머가 종료되었을 때 선택된 옵션이 없다면 투표되지 않고 기권한다.', () => { - const isSelectedOption = false; - const isVoted = false; - - const { result } = renderHook(() => - useTimer({ timeLimit, isSelectedOption, isVoted, vote: voteMock }), - ); - - act(() => { - jest.advanceTimersByTime(timeLimitMs); - }); - - expect(result.current.leftRoundTime).toBe(0); - expect(voteMock).not.toHaveBeenCalled(); - }); - }); });