From 00c726048b60eefcacc74977f3f7e212a5b81337 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 6 Oct 2021 04:47:30 +0700 Subject: [PATCH] Save track id from stream provider (#1048) * save track id from stream provider when favoriting * add youtube provider get track by id * add getStreamForId for bandcamp + audius * update other stream provider * add unit test * playqueue test for favorite * add favorite track container test --- packages/app/app/actions/favorites.ts | 4 +- packages/app/app/actions/queue.ts | 41 +- .../components/FavoriteTracksView/index.js | 1 + .../QueueMenu/QueueMenuMore/index.js | 6 +- .../FavoritesContainer.tracks.test.tsx | 119 +++++ .../FavoritesContainer.tracks.test.tsx.snap | 416 ++++++++++++++++++ .../PlayQueueContainer.test.tsx | 41 ++ .../PlayQueueContainer.test.tsx.snap | 13 +- .../app/containers/QueuePopupButtons/index.js | 1 + packages/app/app/utils.ts | 7 +- packages/app/test/storeBuilders.ts | 70 ++- .../core/src/plugins/stream/AudiusPlugin.ts | 12 + .../core/src/plugins/stream/BandcampPlugin.ts | 14 +- .../src/plugins/stream/InvidiousPlugin.ts | 49 ++- .../core/src/plugins/stream/JamendoPlugin.ts | 29 ++ .../src/plugins/stream/SoundcloudPlugin.ts | 12 + .../core/src/plugins/stream/YoutubePlugin.ts | 9 +- .../src/plugins/stream/iTunesPodcastPlugin.ts | 6 + packages/core/src/plugins/streamProvider.ts | 3 +- packages/core/src/rest/Audius.ts | 4 + packages/core/src/rest/Bandcamp.ts | 9 +- packages/core/src/rest/Invidious.ts | 2 +- packages/core/src/rest/Jamendo.ts | 36 +- packages/core/src/rest/Soundcloud.ts | 4 + packages/core/src/rest/Youtube.ts | 28 +- packages/core/src/rest/audius.test.ts | 61 +++ packages/core/src/rest/bandcamp.test.ts | 6 +- .../ui/lib/components/TrackPopup/index.tsx | 2 + packages/ui/lib/types.ts | 11 + packages/ui/lib/utils.ts | 15 +- 30 files changed, 974 insertions(+), 57 deletions(-) create mode 100644 packages/app/app/containers/FavoritesContainer/FavoritesContainer.tracks.test.tsx create mode 100644 packages/app/app/containers/FavoritesContainer/__snapshots__/FavoritesContainer.tracks.test.tsx.snap diff --git a/packages/app/app/actions/favorites.ts b/packages/app/app/actions/favorites.ts index 72ef56ab54..b70deb3695 100644 --- a/packages/app/app/actions/favorites.ts +++ b/packages/app/app/actions/favorites.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import { store } from '@nuclear/core'; -import { areTracksEqualByName, getTrackItem } from '@nuclear/ui'; +import { areTracksEqualByName, getTrackItem, removeTrackStreamUrl } from '@nuclear/ui'; import { safeAddUuid } from './helpers'; @@ -23,7 +23,7 @@ export function readFavorites() { } export function addFavoriteTrack(track) { - const clonedTrack = safeAddUuid(getTrackItem(track)); + const clonedTrack = safeAddUuid(getTrackItem(removeTrackStreamUrl(track))); const favorites = store.get('favorites'); const filteredTracks = favorites.tracks.filter(t => !areTracksEqualByName(t, track)); diff --git a/packages/app/app/actions/queue.ts b/packages/app/app/actions/queue.ts index e4b3017739..12127b10e0 100644 --- a/packages/app/app/actions/queue.ts +++ b/packages/app/app/actions/queue.ts @@ -3,6 +3,8 @@ import _ from 'lodash'; import { safeAddUuid } from './helpers'; import { startPlayback } from './player.js'; +import { Track } from '@nuclear/ui/lib/types'; +import { StreamProvider } from '@nuclear/core'; export const QUEUE_DROP = 'QUEUE_DROP'; export const ADD_QUEUE_ITEM = 'ADD_QUEUE_ITEM'; @@ -27,6 +29,31 @@ const getSelectedStreamProvider = getState => { return _.find(streamProviders, { sourceName: selected.streamProviders }); }; +const getTrackStream = async (track: Track, selectedStreamProvider: StreamProvider, streamProviders: StreamProvider[]) => { + let streamData; + if (track?.streams && track.streams.length) { + const matchSelectedProvider = track.streams.find( + s => s.source === selectedStreamProvider.sourceName); + + if (matchSelectedProvider && matchSelectedProvider.id) { + streamData = await selectedStreamProvider.getStreamForId(matchSelectedProvider.id); + } else { + const firstKnownProvider = streamProviders.find( + s => s.sourceName === track.streams[0].source); + streamData = await firstKnownProvider.getStreamForId(track.streams[0].id); + } + } + + if (!streamData) { + streamData = await selectedStreamProvider.search({ + artist: typeof track.artist === 'string' ? track.artist : track.artist.name, + track: track.name + }); + } + + return streamData; +}; + const addQueueItem = item => ({ type: ADD_QUEUE_ITEM, payload: { item } @@ -46,7 +73,12 @@ export const addToQueue = (item, asNextItem = false) => async (dispatch, getStat item.loading = !item.local; item = safeAddUuid(item); - const { connectivity } = getState(); + const { + connectivity, + plugin: { + plugins: { streamProviders } + } + } = getState(); const isAbleToAdd = (!connectivity && item.local) || connectivity; isAbleToAdd && dispatch(!asNextItem ? addQueueItem(item) : playNextItem(item)); @@ -54,10 +86,11 @@ export const addToQueue = (item, asNextItem = false) => async (dispatch, getStat if (!item.local && isAbleToAdd) { const selectedStreamProvider = getSelectedStreamProvider(getState); try { - const streamData = await selectedStreamProvider.search({ + const streamData = await getTrackStream({ artist: item.artist, - track: item.name - }); + name: item.name, + streams: item.streams + }, selectedStreamProvider, streamProviders); if (streamData === undefined){ dispatch(removeFromQueue(item)); diff --git a/packages/app/app/components/FavoriteTracksView/index.js b/packages/app/app/components/FavoriteTracksView/index.js index 807cf41195..8463dd5680 100644 --- a/packages/app/app/components/FavoriteTracksView/index.js +++ b/packages/app/app/components/FavoriteTracksView/index.js @@ -94,6 +94,7 @@ const FavoriteTracksView = ({ key={'popular-track-row-' + index} trigger={ + {t('header')} @@ -64,6 +64,7 @@ export const QueueMenuMore = ({ {t('favorite-add')} @@ -115,7 +116,8 @@ export const enhance = compose( { '#text': currentItem.thumbnail } - ] + ], + streams: currentItem.streams }); } }, diff --git a/packages/app/app/containers/FavoritesContainer/FavoritesContainer.tracks.test.tsx b/packages/app/app/containers/FavoritesContainer/FavoritesContainer.tracks.test.tsx new file mode 100644 index 0000000000..97a5cf73f9 --- /dev/null +++ b/packages/app/app/containers/FavoritesContainer/FavoritesContainer.tracks.test.tsx @@ -0,0 +1,119 @@ + +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { createMemoryHistory } from 'history'; +import _ from 'lodash'; + +import { buildStoreState } from '../../../test/storeBuilders'; +import { AnyProps, configureMockStore, setupI18Next, TestRouterProvider, TestStoreProvider } from '../../../test/testUtils'; +import MainContentContainer from '../MainContentContainer'; + +const updateStore = (key: string, value: object) => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const store = require('@nuclear/core').store; + store.set(key, value); +}; + +describe('Track view container', () => { + beforeAll(() => { + setupI18Next(); + }); + + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { store } = require('@nuclear/core'); + store.clear(); + }); + + it('should display favorite tracks', () => { + const favorites = buildStoreState() + .withFavorites() + .build() + .favorites; + updateStore('favorites', favorites); + const { component } = mountComponent(); + expect(component.asFragment()).toMatchSnapshot(); + }); + + it('should display empty state', () => { + const { component } = mountComponent(); + expect(component.asFragment()).toMatchSnapshot(); + }); + + it('should show popup when click on track', async () => { + const favorites = buildStoreState() + .withFavorites() + .build() + .favorites; + updateStore('favorites', favorites); + const { component } = mountComponent(); + + await waitFor(() => component.getByTestId('fav-track-uuid1').click()); + expect(component.asFragment()).toMatchSnapshot(); + }); + + it('should call provider.search when play track with no stream', async () => { + const favorites = buildStoreState() + .withFavorites() + .build() + .favorites; + updateStore('favorites', favorites); + + const { component, store } = mountComponent(); + const state = store.getState(); + const selectedStreamProvider = _.find( + state.plugin.plugins.streamProviders, + { sourceName: state.plugin.selected.streamProviders }); + + await waitFor(() => component.getByTestId('fav-track-uuid2').click()); + await waitFor(() => component.getByTestId('track-popup-add-queue').click()); + + expect(selectedStreamProvider.search).toBeCalled(); + expect(selectedStreamProvider.getStreamForId).not.toBeCalled(); + }); + + it('should call provider.getStreamForId when play track with known stream', async () => { + const favorites = buildStoreState() + .withFavorites() + .build() + .favorites; + updateStore('favorites', favorites); + + const { component, store } = mountComponent(); + const state = store.getState(); + const selectedStreamProvider = _.find( + state.plugin.plugins.streamProviders, + { sourceName: state.plugin.selected.streamProviders }); + + await waitFor(() => component.getByTestId('fav-track-uuid1').click()); + await waitFor(() => component.getByTestId('track-popup-add-queue').click()); + + expect(selectedStreamProvider.search).not.toBeCalled(); + expect(selectedStreamProvider.getStreamForId).toBeCalled(); + }); + + const mountComponent = (initialStore?: AnyProps) => { + const initialState = initialStore || + buildStoreState() + .withPlugins() + .withConnectivity() + .build(); + + const history = createMemoryHistory({ + initialEntries: ['/favorites/tracks'] + }); + const store = configureMockStore(initialState); + const component = render( + + + + + , {container: document.body} + ); + return { component, history, store }; + }; +}); diff --git a/packages/app/app/containers/FavoritesContainer/__snapshots__/FavoritesContainer.tracks.test.tsx.snap b/packages/app/app/containers/FavoritesContainer/__snapshots__/FavoritesContainer.tracks.test.tsx.snap new file mode 100644 index 0000000000..9d59ca7568 --- /dev/null +++ b/packages/app/app/containers/FavoritesContainer/__snapshots__/FavoritesContainer.tracks.test.tsx.snap @@ -0,0 +1,416 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Track view container should display empty state 1`] = ` + +
+
+
+ +