nukeop/nuclear

View on GitHub
packages/app/app/containers/PlayerBarContainer/PlayerBarContainer.test.tsx

Summary

Maintainability
C
1 day
Test Coverage
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';

import { AnyProps, configureMockStore, setupI18Next, TestStoreProvider } from '../../../test/testUtils';
import { getMouseEvent } from '../../../test/mockMouseEvent';
import PlayerBarContainer from '.';
import { buildStoreState } from '../../../test/storeBuilders';

describe('PlayerBar container', () => {
  beforeAll(() => {
    setupI18Next();
  });

  it('should display no time stamp if trackDuration is false', async () => {
    const { component } = mountComponent({
      settings: {
        trackDuration: false
      }
    });

    const timePlayed = component.queryByTestId('track-duration-played');
    expect(timePlayed).toBeNull();
  });

  it('should have an empty seekbar if the current track is still loading', () => {
    const { component } = mountComponent({
      queue: {
        queueItems: [{
          loading: true
        }]
      }
    });

    const fill = component.getByTestId('seekbar-fill');
    const timePlayed = component.queryByTestId('track-duration-played');
    const timeToEnd = component.queryByTestId('track-duration-to-end');

    expect(fill.style.width).toBe('0%');
    expect(timePlayed).toBeNull();
    expect(timeToEnd).toBeNull();
  });

  it('should show a loading play button if the current track is still loading', () => {
    const { component } = mountComponent({
      queue: {
        queueItems: [{
          loading: true
        }]
      }
    });

    const playButton = component.queryByTestId('player-controls-play');
    expect(playButton.children[0].className).toContain('circle notch');
  });

  it.each([
    ['loop', 'loopAfterQueueEnd'], 
    ['shuffle', 'shuffleQueue'],
    ['autoradio', 'autoradio'],
    ['mini-player', 'miniPlayer']
  ])('should enable %s option', async (option, setting) => {
    const { component, store } = mountComponent({
      settings: {
        loopAfterQueueEnd: false,
        shuffleQueue: false,
        autoradio: false,
        miniPlayer: false
      }
    });

    await waitFor(() => component.getByTestId(`${option}-play-option`).click());
    const state = store.getState();
    expect(state.settings[setting]).toBe(true);
  });

  // Has to be skipped until jsdom supports setting clientWidth on the body
  xit('should seek to a particular place in the current track when the seekbar is clicked', async () => {
    const { component, store } = mountComponent();

    const seekbar = await component.findByTestId('seekbar');
    fireEvent(seekbar, getMouseEvent('click', {
      pageX: 100
    }));
    const state = store.getState();

    const documentWidth = 200;
    // set document width to the above value here

    expect(state.player.seek).toBe(100 / documentWidth);
  });
  
  it('should start playing the current track when the play button is clicked', async () => {
    const { component, store } = mountComponent();
    const playButton = await component.findByTestId('player-controls-play');
    fireEvent.click(playButton);
    const state = store.getState();
    expect(state.player.playbackStatus).toBe('PLAYING');
  });

  it('should pause the current track when the pause button is clicked', async () => {
    const { component, store } = mountComponent({
      player: {
        playbackStatus: 'PLAYING'
      }
    });
    const pauseButton = await component.findByTestId('player-controls-play');
    expect(pauseButton.children[0].className).toContain('pause');

    fireEvent.click(pauseButton);
    const state = store.getState();
    expect(state.player.playbackStatus).toBe('PAUSED');
  });

  it('should skip to the next track when the next button is clicked', async () => {
    const { component, store } = mountComponent();
    const nextButton = await component.findByTestId('player-controls-forward');
    fireEvent.click(nextButton);
    const state = store.getState();
    expect(state.queue.currentSong).toBe(1);
  });

  it('should rewind to the beginning of the current track when the previous button is clicked and the track has progressed past the first 3 seconds', async () => {
    const { component, store } = mountComponent({
      queue: {
        currentSong: 0,
        queueItems: [{
          artist: 'test artist 1',
          name: 'test track 1',
          streams: [{
            duration: 300,
            title: 'test track 1',
            skipSegments: [],
            stream: 'stream URL'
          }]
        }]
      },
      player: {
        seek: 5
      }
    });
    const previousButton = await component.findByTestId('player-controls-back');
    fireEvent.click(previousButton);
    const state = store.getState();
    waitFor(() => expect(state.player.seek).toBe(0));
    expect(state.queue.currentSong).toBe(0);
  });

  it('should skip to the previous track when the previous button is clicked and the track has not progressed past the first 3 seconds', async () => {
    const { component, store } = mountComponent({
      queue: {
        currentSong: 1,
        queueItems: [{
          artist: 'test artist 1',
          name: 'test track 1',
          streams: [{
            duration: 300,
            title: 'test track 1',
            skipSegments: [],
            stream: 'stream URL 1'
          }]
        }, {
          artist: 'test artist 2',
          name: 'test track 2',
          streams: [{
            duration: 300,
            title: 'test track 2',
            skipSegments: [],
            stream: 'stream URL 2'
          }]
        }]
      },
      player: {
        seek: 0
      }
    });
    const previousButton = await component.findByTestId('player-controls-back');
    fireEvent.click(previousButton);
    const state = store.getState();
    expect(state.player.seek).toBe(0);
    expect(state.queue.currentSong).toBe(0);
  });

  it('should skip to the previous track if there is a sponsorblock segment at the beginning of the current track and the playhead is within 3 seconds of the segment', async () => {
    const { component, store } = mountComponent({
      queue: {
        currentSong: 1,
        queueItems: [{
          artist: 'test artist 1',
          name: 'test track 1',
          streams: [{
            duration: 300,
            title: 'test track 1',
            skipSegments: [],
            stream: 'stream URL 1'
          }]
        }, {
          artist: 'test artist 2',
          name: 'test track 2',
          streams: [{
            duration: 300,
            title: 'test track 2',
            skipSegments: [{
              category: 'sponsor',
              duration: 10,
              endTime: 10,
              startTime: 0
            }],
            stream: 'stream URL 2'
          }]
        }]
      },
      player: {
        seek: 12
      }
    });
    const previousButton = await component.findByTestId('player-controls-back');
    fireEvent.click(previousButton);
    const state = store.getState();
    expect(state.player.seek).toBe(0);
    expect(state.queue.currentSong).toBe(0); 
  });

  it('should go back to the beginning of current track if current track is the first song in the queue', async () => {
    const { component, store } = mountComponent({
      queue: {
        currentSong: 0,
        queueItems: [{
          artist: 'test artist 1',
          name: 'test track 1',
          streams: [{
            duration: 300,
            title: 'test track 1',
            skipSegments: [],
            stream: 'stream URL'
          }]
        }]
      },
      player: {
        seek: 12
      }
    });
    const previousButton = await component.findByTestId('player-controls-back');
    fireEvent.click(previousButton);
    const state = store.getState();
    expect(state.player.seek).toBe(0);
    expect(state.queue.currentSong).toBe(0); 
  });

  it('should remove the track when no streams are available for the track', async () => {
    const { component, store } = mountComponent({
      queue: {
        currentSong: 0,
        queueItems: [
          {
            uuid: 'uuid1',
            artist: 'test artist name',
            name: 'track without streams'
          }
        ]
      },
      plugin: {
        plugins: {
          streamProviders: [
            {
              sourceName: 'Mocked Stream Provider',
              search: jest.fn().mockResolvedValueOnce([])
            }
          ]
        },
        selected: {
          streamProviders: 'Mocked Stream Provider'
        }
      }
    });
    await waitFor(() => {
      const state = store.getState();
      return expect(state.queue.queueItems.length).toBe(0);
    });
  });

  it('should remove the track when no more stream URLs can be resolved', async () => {
    const { component, store } = mountComponent({
      queue: {
        currentSong: 0,
        queueItems: [
          {
            uuid: 'uuid1',
            artist: 'test artist name',
            name: 'track without streams'
          }
        ]
      },
      plugin: {
        plugins: {
          streamProviders: [
            {
              sourceName: 'Mocked Stream Provider',
              search: jest.fn().mockResolvedValueOnce([
                {
                  id: 'stream 1 ID',
                  source: 'Mocked Stream Provider'
                },
                {
                  id: 'stream 2 ID',
                  source: 'Mocked Stream Provider'
                }
              ]),
              getStreamForId: jest.fn()
                .mockResolvedValueOnce(null)
                .mockResolvedValueOnce({
                  stream: null,
                  source: 'Mocked Stream Provider'
                })
            }
          ]
        },
        selected: {
          streamProviders: 'Mocked Stream Provider'
        }
      }
    });
    await waitFor(() => {
      const state = store.getState();
      expect(state.queue.queueItems.length).toBe(0);
    });
  });

  const mountComponent = (initialStore?: AnyProps) => {
    const store = configureMockStore({
      ...buildStoreState()
        .withTracksInPlayQueue()
        .withSettings({
          trackDuration: true,
          skipSponsorblock: true
        })
        .build(),
      ...initialStore
    });

    const component = render(<TestStoreProvider
      store={store}
    >
      <PlayerBarContainer />
    </TestStoreProvider>);
    return { component, store };
  };
});