I tried to write the test for the countdown file using jest
This is my countdown file:
import React, { useEffect, useState } from 'react'
import _padStart from 'lodash/padStart'
import styles from './CountDown.module.scss'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration' // Import the duration plugin
import cx from 'classnames'
import useCountDown from '../utils/useCountDown'
import dynamic from 'next/dynamic'
import { twMerge } from 'tailwind-merge'
dayjs.extend(duration) // Extend dayjs with the duration plugin
const worker_script = dynamic(
() => import('utils').then((mod) => mod.timerWorker),
{ ssr: false }
)
const CountDown = ({ startTime, containerClassName, onCompleteTimer }) => {
const [lectureStartWorker, setLectureStartWorker] = useState(null)
const [timerData, setTimerData] = useState({
day: 0,
hour: 0,
minute: 0,
})
const [showCountDown, setShowCountdown] = useState(false)
useEffect(() => {
setLectureStartWorker(new Worker(worker_script))
return () => {
lectureStartWorker?.postMessage({
action: 'stop',
payload: null,
})
lectureStartWorker?.terminate?.()
}
}, [])
useEffect(() => {
if (!lectureStartWorker) return
if (startTime) {
const currentTime = dayjs(new Date())
const _startTime = dayjs(startTime)
const duration = _startTime.diff(currentTime, 'seconds')
if (duration >= 0) handleLectureStartTimer(duration * 1000)
}
}, [lectureStartWorker, startTime])
useEffect(() => {
const { day, hour, minute } = timerData
if (day || hour || minute) {
setShowCountdown(true)
} else {
setShowCountdown(false)
onCompleteTimer && onCompleteTimer()
}
}, [timerData])
const handleLectureStartTimer = (duration) => {
handleCountdown(duration)
lectureStartWorker &&
lectureStartWorker.postMessage({
action: 'start',
payload: {
interval: 1000 * 60,
duration: duration,
},
})
lectureStartWorker.onmessage = (event) => {
let timeLeft = event.data || 0
handleCountdown(timeLeft)
}
}
const handleCountdown = (timeLeft) => {
const _data = dayjs.duration(timeLeft)
setTimerData({
day: parseInt(_data.asDays()),
hour: _data.hours(),
minute: _data.minutes(),
})
}
return (
showCountDown && (
<div className={twMerge(cx(styles.container, containerClassName))}>
<div className={styles.textDiv}>Class starts in</div>
<div className={styles.timeContainer}>
<div className={styles.timeDiv}>
<span role="timer" aria-label="days">
{timerData.day ? _padStart(timerData.day, 2, 0) : '00'}
</span>
<span>:</span>
<span role="timer" aria-label="hours">
{timerData.hour ? _padStart(timerData.hour, 2, 0) : '00'}
</span>
<span>:</span>
<span role="timer" aria-label="minutes">
{timerData.minute ? _padStart(timerData.minute, 2, 0) : '00'}
</span>
</div>
<div className={styles.timeLabelDiv}>
<span className={styles.daysLabel}>Days</span>
<span className={styles.hoursLabel}>Hrs</span>
<span className={styles.minutesLabel}>Mins</span>
</div>
</div>
</div>
)
)
}
export default CountDown
And the timerworker.js file as below (this is the web worker script) :
const timerWorker = () => {
let timerStart = false
let intervalId = null
let timeLeft = 0
self.onmessage = (e) => {
const { data = {} } = e
const { action, payload } = data
switch (action) {
case 'start':
start(payload)
break
case 'stop':
stop()
break
default:
self.postMessage('please pass a valid action')
break
}
}
function start(payload) {
const { interval = 1000, duration = 0 } = payload
stop()
timeLeft = duration
if (duration >= 0) self.postMessage(timeLeft)
intervalId = setInterval(function () {
timeLeft -= interval
if (timeLeft <= 0) {
stop()
} else {
self.postMessage(timeLeft)
}
}, interval)
timerStart = true
}
function stop() {
clearInterval(intervalId)
timerStart = false
timeLeft = 0
self.postMessage(timeLeft)
}
}
let code = timerWorker.toString()
code = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'))
const blob =
typeof Blob !== 'undefined'
? new Blob([code], { type: 'application/javascript' })
: {}
const worker_script =
typeof Blob !== 'undefined' ? URL.createObjectURL(blob) : {}
export default worker_script
I have tried writing this test case and it runs :
import React from 'react'
import { render, screen, act } from '@testing-library/react'
import CountDown from './CountDown'
import dayjs from 'dayjs'
// Mock the dynamic import for the worker script
jest.mock('next/dynamic', () => () => jest.fn())
// Mock the worker constructor
class MockWorker {
constructor() {
this.onmessage = null
}
postMessage(message) {
// Simulating the worker behavior based on the action
if (message.action === 'start') {
const duration = message.payload.duration
let timeLeft = duration
const interval = setInterval(() => {
if (timeLeft <= 0) {
clearInterval(interval)
this.onmessage({ data: 0 })
} else {
timeLeft -= message.payload.interval
this.onmessage({ data: timeLeft }) // Post the remaining time
}
}, message.payload.interval)
}
if (message.action === 'stop') {
this.onmessage({ data: 0 })
}
}
terminate() {
// Mock terminate
}
}
describe('CountDown Component', () => {
const mockStartTime = dayjs().add(1, 'hour').toISOString()
beforeEach(() => {
jest.clearAllMocks()
global.Worker = MockWorker // Overriding global Worker with mock
jest.useFakeTimers()
})
test('renders countdown component with initial values', () => {
render(<CountDown startTime={mockStartTime} />)
// Check for initial timer labels
expect(screen.getByRole('timer', { name: 'days' })).toHaveTextContent('00')
expect(screen.getByRole('timer', { name: 'hours' })).toHaveTextContent('00')
expect(screen.getByRole('timer', { name: 'minutes' })).toHaveTextContent('59')
})
test('updates time left as the countdown progresses', async () => {
render(<CountDown startTime={mockStartTime} />)
// Fast-forward by 30 minutes
act(() => {
jest.advanceTimersByTime(30 * 60 * 1000) // 30 minutes
})
//Expected results after 30 minutes have passed
expect(screen.getByRole('timer', { name: 'hours' })).toHaveTextContent('00')
expect(screen.getByRole('timer', { name: 'minutes' })).toHaveTextContent('29')
})
})
But I don’t want to use class based code and dont want to mock a new function I want to see the component rendering when it uses the timerworker.js file as web worker and renders the time.