|
1 | 1 | import React from 'react'
|
2 |
| -import { render } from '@testing-library/react' |
| 2 | +import { render, act } from '@testing-library/react' |
3 | 3 | import Duration from './Duration'
|
4 | 4 |
|
| 5 | +function createMockAudio({ duration = 0 } = {}) { |
| 6 | + const listeners = {} |
| 7 | + const audio = { |
| 8 | + duration, |
| 9 | + addEventListener: jest.fn((event, cb) => { |
| 10 | + listeners[event] = cb |
| 11 | + }), |
| 12 | + removeEventListener: jest.fn((event) => { |
| 13 | + delete listeners[event] |
| 14 | + }), |
| 15 | + dispatch(event) { |
| 16 | + if (listeners[event]) listeners[event]({ target: audio }) |
| 17 | + }, |
| 18 | + } |
| 19 | + return audio |
| 20 | +} |
| 21 | + |
5 | 22 | describe('Duration component', () => {
|
6 |
| - it('renders with null default (no crash)', () => { |
| 23 | + const defaultDisplay = '00:00' |
| 24 | + |
| 25 | + it('renders defaultDuration when no audio provided', () => { |
| 26 | + const { container } = render( |
| 27 | + <Duration defaultDuration={defaultDisplay} timeFormat="auto" /> |
| 28 | + ) |
| 29 | + expect(container.textContent).toBe(defaultDisplay) |
| 30 | + }) |
| 31 | + |
| 32 | + it('displays duration (mm:ss) with audio and format auto (< 1h)', () => { |
| 33 | + const audio = createMockAudio({ duration: 305 }) // 5:05 |
| 34 | + const { container } = render( |
| 35 | + <Duration audio={audio} defaultDuration={defaultDisplay} timeFormat="auto" /> |
| 36 | + ) |
| 37 | + // Initial render already computes value |
| 38 | + expect(container.textContent).toBe('05:05') |
| 39 | + }) |
| 40 | + |
| 41 | + it('displays duration (hh:mm:ss) when auto and >= 3600', () => { |
| 42 | + const audio = createMockAudio({ duration: 3661 }) // 1:01:01 |
| 43 | + const { container } = render( |
| 44 | + <Duration audio={audio} defaultDuration={defaultDisplay} timeFormat="auto" /> |
| 45 | + ) |
| 46 | + expect(container.textContent).toBe('1:01:01') |
| 47 | + }) |
| 48 | + |
| 49 | + it('honors explicit mm:ss', () => { |
| 50 | + const audio = createMockAudio({ duration: 3661 }) |
| 51 | + const { container } = render( |
| 52 | + <Duration audio={audio} defaultDuration={defaultDisplay} timeFormat="mm:ss" /> |
| 53 | + ) |
| 54 | + // Even though >= 1h, should truncate to minutes only |
| 55 | + expect(container.textContent).toBe('61:01') |
| 56 | + }) |
| 57 | + |
| 58 | + it('honors explicit hh:mm:ss', () => { |
| 59 | + const audio = createMockAudio({ duration: 59 }) |
| 60 | + const { container } = render( |
| 61 | + <Duration audio={audio} defaultDuration={defaultDisplay} timeFormat="hh:mm:ss" /> |
| 62 | + ) |
| 63 | + expect(container.textContent).toBe('0:00:59') |
| 64 | + }) |
| 65 | + |
| 66 | + it('updates on durationchange event', () => { |
| 67 | + const audio = createMockAudio({ duration: 10 }) |
7 | 68 | const { container } = render(
|
8 |
| - // @ts-ignore testing JS placeholder |
9 |
| - React.createElement(Duration, { defaultDuration: null, timeFormat: 'auto' }) |
| 69 | + <Duration audio={audio} defaultDuration={defaultDisplay} timeFormat="mm:ss" /> |
| 70 | + ) |
| 71 | + expect(container.textContent).toBe('00:10') |
| 72 | + audio.duration = 25 |
| 73 | + act(() => { |
| 74 | + audio.dispatch('durationchange') |
| 75 | + }) |
| 76 | + expect(container.textContent).toBe('00:25') |
| 77 | + }) |
| 78 | + |
| 79 | + it('falls back to defaultDuration after event when initial formatter returns null (NaN)', () => { |
| 80 | + const audio = createMockAudio({ duration: NaN }) |
| 81 | + const { container } = render( |
| 82 | + <Duration audio={audio} defaultDuration={defaultDisplay} timeFormat="auto" /> |
| 83 | + ) |
| 84 | + // Initial state stores null directly (renders empty string) |
| 85 | + expect(container.textContent).toBe('') |
| 86 | + act(() => { |
| 87 | + audio.dispatch('durationchange') |
| 88 | + }) |
| 89 | + expect(container.textContent).toBe(defaultDisplay) |
| 90 | + }) |
| 91 | + |
| 92 | + it('adds listeners only once across re-renders', () => { |
| 93 | + const audio = createMockAudio({ duration: 100 }) |
| 94 | + const { rerender } = render( |
| 95 | + <Duration audio={audio} defaultDuration={defaultDisplay} timeFormat="mm:ss" /> |
| 96 | + ) |
| 97 | + // Re-render with different timeFormat |
| 98 | + rerender( |
| 99 | + <Duration audio={audio} defaultDuration={defaultDisplay} timeFormat="hh:mm:ss" /> |
| 100 | + ) |
| 101 | + expect(audio.addEventListener).toHaveBeenCalledTimes(2) // durationchange + abort |
| 102 | + }) |
| 103 | + |
| 104 | + it('removes listeners on unmount', () => { |
| 105 | + const audio = createMockAudio({ duration: 50 }) |
| 106 | + const { unmount } = render( |
| 107 | + <Duration audio={audio} defaultDuration={defaultDisplay} timeFormat="mm:ss" /> |
10 | 108 | )
|
11 |
| - expect(container).toBeTruthy() |
| 109 | + unmount() |
| 110 | + expect(audio.removeEventListener).toHaveBeenCalledTimes(2) |
12 | 111 | })
|
13 | 112 | })
|
14 | 113 |
|
0 commit comments