UNPKG

@wordpress/block-library

Version:
507 lines (424 loc) 13.4 kB
import '@testing-library/jest-dom'; import { act, render } from '@testing-library/react'; import { WaveformPlayer } from '../waveform-player'; import { applyWaveformPlayerStyles, initWaveformPlayer, setupPlayButtonArtwork, } from '../waveform-utils'; jest.mock( '../waveform-utils', () => ( { applyWaveformPlayerStyles: jest.fn(), initWaveformPlayer: jest.fn(), setupPlayButtonArtwork: jest.fn(), updateSeekControlLabel: jest.fn(), } ) ); /** * Create a fake player instance that mimics the parts of the waveform * player instance the component manipulates. * * @param {Object} options Options passed to initWaveformPlayer. * @param {Element} element The element passed to initWaveformPlayer. * @return {Object} The fake player. */ function createFakePlayer( options, element ) { const titleEl = document.createElement( 'span' ); titleEl.textContent = options.title ?? ''; // The artist and artwork elements only exist when the track had an // artist/player artwork when the player was created, mirroring the library markup. let artistEl = null; if ( options.artist ) { artistEl = document.createElement( 'span' ); artistEl.textContent = options.artist; } let artworkEl = null; if ( options.image && ! options.showPlayButtonArtwork ) { artworkEl = document.createElement( 'img' ); artworkEl.src = options.image; artworkEl.alt = options.imageAlt || ''; } element.append( titleEl ); if ( artistEl ) { element.append( artistEl ); } if ( artworkEl ) { element.append( artworkEl ); } const instance = { titleEl, artistEl: artistEl || null, artworkEl: artworkEl || null, pause: jest.fn(), syncArtist: jest.fn( ( artist ) => { if ( ! artist ) { instance.artistEl?.remove(); instance.artistEl = null; return; } if ( ! instance.artistEl ) { instance.artistEl = document.createElement( 'span' ); element.append( instance.artistEl ); } instance.artistEl.textContent = artist; instance.artistEl.style.display = ''; } ), syncArtwork: jest.fn( ( image, imageAlt = '' ) => { if ( ! image ) { instance.artworkEl?.remove(); instance.artworkEl = null; return; } if ( ! instance.artworkEl ) { instance.artworkEl = document.createElement( 'img' ); element.append( instance.artworkEl ); } instance.artworkEl.src = image; instance.artworkEl.alt = imageAlt || ''; } ), loadTrack: jest.fn( async ( src, title, artist, trackOptions ) => { titleEl.textContent = title; instance.syncArtist( artist ); instance.syncArtwork( trackOptions.artwork, trackOptions.artworkAlt ); } ), }; return { instance, container: element, destroy: jest.fn(), }; } describe( 'WaveformPlayer', () => { beforeEach( () => { jest.useFakeTimers(); initWaveformPlayer.mockImplementation( ( element, options ) => createFakePlayer( options, element ) ); } ); afterEach( () => { jest.runOnlyPendingTimers(); jest.useRealTimers(); applyWaveformPlayerStyles.mockReset(); initWaveformPlayer.mockReset(); setupPlayButtonArtwork.mockReset(); } ); const baseProps = { src: 'https://example.com/song.mp3', title: 'Original Title', artist: 'Original Artist', image: 'https://example.com/cover.jpg', imageAlt: 'A bright abstract track image', onEnded: () => {}, }; it( 'initializes the player once with the provided metadata', () => { render( <WaveformPlayer { ...baseProps } /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 1 ); expect( initWaveformPlayer ).toHaveBeenCalledWith( expect.anything(), expect.objectContaining( { src: baseProps.src, title: 'Original Title', artist: 'Original Artist', image: 'https://example.com/cover.jpg', imageAlt: 'A bright abstract track image', showPlayButtonArtwork: false, } ) ); } ); it( 'passes the play button artwork option to the shared player', () => { render( <WaveformPlayer { ...baseProps } showPlayButtonArtwork /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); expect( initWaveformPlayer ).toHaveBeenCalledWith( expect.anything(), expect.objectContaining( { showPlayButtonArtwork: true, } ) ); } ); it( 'initializes the player with custom color values', () => { render( <WaveformPlayer { ...baseProps } color="#ff0000" gradient="linear-gradient(90deg,#ff0000 0%,#0000ff 100%)" backgroundColor="#ffeeaa" backgroundGradient="linear-gradient(90deg,#ffeeaa 0%,#aabbcc 100%)" textColor="#0000ff" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); expect( initWaveformPlayer ).toHaveBeenCalledWith( expect.anything(), expect.objectContaining( { waveformColor: '#ff0000', waveformGradient: 'linear-gradient(90deg,#ff0000 0%,#0000ff 100%)', backgroundColor: '#ffeeaa', backgroundGradient: 'linear-gradient(90deg,#ffeeaa 0%,#aabbcc 100%)', textColor: '#0000ff', } ) ); } ); it( 'omits separate player artwork when play button artwork is enabled', () => { render( <WaveformPlayer { ...baseProps } showPlayButtonArtwork /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; expect( player.instance.artworkEl ).toBeNull(); } ); it( 'updates play button artwork when artwork metadata changes', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } showPlayButtonArtwork /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; rerender( <WaveformPlayer { ...baseProps } image="https://example.com/new.jpg" showPlayButtonArtwork /> ); expect( setupPlayButtonArtwork ).toHaveBeenCalledWith( player.container, 'https://example.com/new.jpg' ); expect( player.instance.artworkEl ).toBeNull(); } ); it( 'updates metadata on the live player without recreating it', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; rerender( <WaveformPlayer { ...baseProps } title="New Title" artist="New Artist" image="https://example.com/new.jpg" imageAlt="A black and white portrait" /> ); // The player is updated in place, not destroyed and recreated. expect( initWaveformPlayer ).toHaveBeenCalledTimes( 1 ); expect( player.destroy ).not.toHaveBeenCalled(); expect( player.instance.titleEl ).toHaveTextContent( 'New Title' ); expect( player.instance.artistEl ).toHaveTextContent( 'New Artist' ); expect( player.instance.artworkEl ).toHaveAttribute( 'src', 'https://example.com/new.jpg' ); expect( player.instance.artworkEl ).toHaveAttribute( 'alt', 'A black and white portrait' ); } ); it( 'does not recreate the player when the src changes', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; rerender( <WaveformPlayer { ...baseProps } src="https://example.com/other.mp3" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); expect( player.destroy ).not.toHaveBeenCalled(); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 1 ); expect( player.instance.loadTrack ).toHaveBeenCalledWith( 'https://example.com/other.mp3', 'Original Title', 'Original Artist', { artwork: 'https://example.com/cover.jpg', artworkAlt: 'A bright abstract track image', } ); } ); it( 'recreates the player when play button artwork is toggled', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } showPlayButtonArtwork={ false } /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; rerender( <WaveformPlayer { ...baseProps } showPlayButtonArtwork /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); expect( player.destroy ).toHaveBeenCalledTimes( 1 ); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 2 ); expect( initWaveformPlayer.mock.calls[ 1 ][ 1 ] ).toEqual( expect.objectContaining( { showPlayButtonArtwork: true, } ) ); expect( initWaveformPlayer.mock.results[ 1 ].value.instance.artworkEl ).toBeNull(); } ); it( 'recreates the player when the waveform color changes', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } color="#ff0000" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; rerender( <WaveformPlayer { ...baseProps } color="#0000ff" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); expect( player.destroy ).toHaveBeenCalledTimes( 1 ); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 2 ); expect( initWaveformPlayer ).toHaveBeenLastCalledWith( expect.anything(), expect.objectContaining( { waveformColor: '#0000ff', } ) ); } ); it( 'updates the waveform background color without recreating the player', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } backgroundColor="#ffeeaa" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; rerender( <WaveformPlayer { ...baseProps } backgroundColor="#aabbcc" /> ); expect( player.destroy ).not.toHaveBeenCalled(); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 1 ); expect( applyWaveformPlayerStyles ).toHaveBeenCalledWith( player.container, { backgroundColor: '#aabbcc', backgroundGradient: undefined, playButtonColor: undefined, playButtonGradient: undefined, textColor: undefined, } ); } ); it( 'recreates the player when the waveform gradient changes', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } gradient="linear-gradient(90deg,#ff0000 0%,#0000ff 100%)" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; rerender( <WaveformPlayer { ...baseProps } gradient="linear-gradient(90deg,#00ff00 0%,#0000ff 100%)" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); expect( player.destroy ).toHaveBeenCalledTimes( 1 ); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 2 ); expect( initWaveformPlayer ).toHaveBeenLastCalledWith( expect.anything(), expect.objectContaining( { waveformGradient: 'linear-gradient(90deg,#00ff00 0%,#0000ff 100%)', } ) ); } ); it( 'updates the player in place to show an image added to a track that had none', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } image="" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const firstPlayer = initWaveformPlayer.mock.results[ 0 ].value; // No artwork element exists when the track started without an image. expect( firstPlayer.instance.artworkEl ).toBeNull(); rerender( <WaveformPlayer { ...baseProps } image="https://example.com/added.jpg" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); expect( firstPlayer.destroy ).not.toHaveBeenCalled(); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 1 ); expect( firstPlayer.instance.artworkEl ).toHaveAttribute( 'src', 'https://example.com/added.jpg' ); } ); it( 'updates the player in place when the image is removed', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; rerender( <WaveformPlayer { ...baseProps } image="" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); expect( player.destroy ).not.toHaveBeenCalled(); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 1 ); expect( player.instance.artworkEl ).toBeNull(); } ); it( 'updates the player in place to show an artist added to a track that had none', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } artist="" /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const firstPlayer = initWaveformPlayer.mock.results[ 0 ].value; expect( firstPlayer.instance.artistEl ).toBeNull(); rerender( <WaveformPlayer { ...baseProps } artist="New Artist" /> ); expect( firstPlayer.destroy ).not.toHaveBeenCalled(); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 1 ); expect( firstPlayer.instance.artistEl ).toHaveTextContent( 'New Artist' ); expect( firstPlayer.instance.artistEl ).not.toHaveStyle( { display: 'none', } ); } ); it( 'updates the player in place when the artist is removed', () => { const { rerender } = render( <WaveformPlayer { ...baseProps } /> ); act( () => { jest.advanceTimersByTime( 100 ); } ); const player = initWaveformPlayer.mock.results[ 0 ].value; rerender( <WaveformPlayer { ...baseProps } artist="" /> ); expect( player.destroy ).not.toHaveBeenCalled(); expect( initWaveformPlayer ).toHaveBeenCalledTimes( 1 ); expect( player.instance.artistEl ).toBeNull(); } ); } );