barCallBack(event, timeRef, setProgress)}
- onKeyDown={() => dispatch(playPause())}
+ onKeyDown={() => playPause()}
// biome-ignore lint/a11y/useSemanticElements: clickable div is fine
role="button"
tabIndex={0}
diff --git a/src/index.tsx b/src/index.tsx
index ad240fb..f9943ad 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,17 +1,13 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
-import { Provider } from 'react-redux';
import App from './App';
import './index.scss';
-import { store } from './store/store';
const container = document.getElementById('root');
// biome-ignore lint/style/noNonNullAssertion:
const root = createRoot(container!);
root.render(
-
-
-
+
,
);
diff --git a/src/pages/PlaylistDetail/PlaylistDetail.spec.tsx b/src/pages/PlaylistDetail/PlaylistDetail.spec.tsx
index e9a4708..9737977 100644
--- a/src/pages/PlaylistDetail/PlaylistDetail.spec.tsx
+++ b/src/pages/PlaylistDetail/PlaylistDetail.spec.tsx
@@ -1,54 +1,82 @@
import { cleanup, render, screen } from '@testing-library/react';
-import { Provider } from 'react-redux';
-import { afterEach, describe, expect, test } from 'vitest';
+import { MemoryRouter } from 'react-router-dom';
+import { afterEach, describe, expect, test, vi } from 'vitest';
import { mockPlaylistDetails } from '../../../tests/mockData';
-import { createMockStore } from '../../store/store';
+import { createTestStore, setupTestStoreMocks } from '../../store/test-utils';
+import * as zustandStore from '../../store/zustand-store';
import PlaylistDetail from './PlaylistDetail';
-const store = createMockStore({
- playlistDetail: {
- playlist: mockPlaylistDetails,
- loading: false,
- error: '',
- },
-});
-
-const loadingStore = createMockStore({
- playlistDetail: {
- playlist: null,
- loading: true,
- error: '',
- },
-});
+vi.mock('../../store/zustand-store');
describe('Playlist details', () => {
- afterEach(cleanup);
+ afterEach(() => {
+ cleanup();
+ vi.clearAllMocks();
+ });
+
+ test('should render loader when playlist is loading', async () => {
+ const testStore = createTestStore({
+ playlistDetail: {
+ playlist: null,
+ loading: true,
+ error: '',
+ },
+ currentSong: {
+ song: null,
+ playing: false,
+ },
+ });
+ setupTestStoreMocks(testStore, zustandStore, vi);
- test('should render loader', async () => {
render(
-
+
- ,
+ ,
);
expect(screen.getByRole('progressbar', { busy: true })).toBeTruthy();
});
- test('should render playlist details', async () => {
+ test('should render playlist details when loaded', async () => {
+ const testStore = createTestStore({
+ playlistDetail: {
+ playlist: mockPlaylistDetails,
+ loading: false,
+ error: '',
+ },
+ currentSong: {
+ song: null,
+ playing: false,
+ },
+ });
+ setupTestStoreMocks(testStore, zustandStore, vi);
render(
-
+
- ,
+ ,
);
- expect(screen.findByText('Hits du Moment')).toBeTruthy();
+ expect(await screen.findByText('Hits du Moment')).toBeTruthy();
});
- // test('should have a background color', async () => {
- // render(
- //
- //
- // ,
- // );
-
- // expect(screen.getByTestId('Background').style.backgroundColor).toBeTruthy();
- // });
+ test('should render error state when there is an error', async () => {
+ const testStore = createTestStore({
+ playlistDetail: {
+ playlist: null,
+ loading: false,
+ error: 'Failed to load playlist',
+ },
+ currentSong: {
+ song: null,
+ playing: false,
+ },
+ });
+ setupTestStoreMocks(testStore, zustandStore, vi);
+ render(
+
+
+ ,
+ );
+ // The error handling would depend on how the component handles error states
+ // For now, we just verify the component renders without crashing
+ expect(screen.queryByRole('progressbar', { busy: true })).toBeFalsy();
+ });
});
diff --git a/src/pages/PlaylistDetail/PlaylistDetail.tsx b/src/pages/PlaylistDetail/PlaylistDetail.tsx
index d6cc00d..15b4bd5 100644
--- a/src/pages/PlaylistDetail/PlaylistDetail.tsx
+++ b/src/pages/PlaylistDetail/PlaylistDetail.tsx
@@ -4,12 +4,11 @@ import { useParams } from 'react-router-dom';
import Time from '../../assets/time.svg?react';
import Loader from '../../components/Loader/Loader';
import NotFound from '../../components/NotFound/NotFound';
-import { useAppDispatch, useAppSelector } from '../../store/hooks';
-import { selectCurrentSong } from '../../store/reducers/currentSong.slice';
import {
- fetchPlaylistById,
- playlistDetailsSelector,
-} from '../../store/reducers/playlistDetail.slice';
+ useAppStore,
+ useCurrentSong,
+ usePlaylistDetail,
+} from '../../store/zustand-store';
import msToMinutesAndSeconds from '../../utils/msToMinutes';
import styles from './PlaylistDetail.module.scss';
import SongItem from './SongItem/SongItem';
@@ -17,15 +16,15 @@ import SongItem from './SongItem/SongItem';
const PlaylistDetail = () => {
const { id } = useParams<{ id: string }>();
const coverRef = useRef(null);
- const dispatch = useAppDispatch();
- const { playlist, loading, error } = useAppSelector(playlistDetailsSelector);
- const { song } = useAppSelector(selectCurrentSong);
+ const fetchPlaylistById = useAppStore((state) => state.fetchPlaylistById);
+ const { playlist, loading, error } = usePlaylistDetail();
+ const { song } = useCurrentSong();
useEffect(() => {
if (id != null) {
- void dispatch(fetchPlaylistById(id));
+ void fetchPlaylistById(id);
}
- }, [id, dispatch]);
+ }, [id, fetchPlaylistById]);
useEffect(() => {
if (coverRef.current != null) {
diff --git a/src/pages/PlaylistDetail/SongItem/SongItem.tsx b/src/pages/PlaylistDetail/SongItem/SongItem.tsx
index 79cc742..7e27589 100644
--- a/src/pages/PlaylistDetail/SongItem/SongItem.tsx
+++ b/src/pages/PlaylistDetail/SongItem/SongItem.tsx
@@ -1,6 +1,5 @@
import Play from '../../../assets/play.svg?react';
-import { useAppDispatch } from '../../../store/hooks';
-import { loadSong } from '../../../store/reducers/currentSong.slice';
+import { useAppStore } from '../../../store/zustand-store';
import type { Track } from '../../../types/track.interface';
import formatDate from '../../../utils/formatDate';
import msToMinutesAndSeconds from '../../../utils/msToMinutes';
@@ -13,12 +12,12 @@ interface SongItemPros {
}
const SongItem = ({ song, index, current }: SongItemPros) => {
- const dispatch = useAppDispatch();
+ const loadSong = useAppStore((state) => state.loadSong);
const previewAvailable = song.track?.preview_url !== null;
const handleSongClick = (): void => {
if (previewAvailable) {
- dispatch(loadSong(song));
+ loadSong(song);
}
};
diff --git a/src/store/hooks.ts b/src/store/hooks.ts
deleted file mode 100644
index f76bd0c..0000000
--- a/src/store/hooks.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import {
- type TypedUseSelectorHook,
- useDispatch,
- useSelector,
-} from 'react-redux';
-import type { AppDispatch, RootState } from './store';
-
-// Use throughout your app instead of plain `useDispatch` and `useSelector`
-export const useAppDispatch = () => useDispatch();
-export const useAppSelector: TypedUseSelectorHook = useSelector;
diff --git a/src/store/reducers/currentSong.slice.ts b/src/store/reducers/currentSong.slice.ts
deleted file mode 100644
index 5c51468..0000000
--- a/src/store/reducers/currentSong.slice.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { type PayloadAction, createSlice } from '@reduxjs/toolkit';
-import type { Track } from '../../types/track.interface';
-import type { RootState } from '../store';
-
-interface CurrentSongState {
- playing: boolean;
- song: Track | null;
-}
-
-export const initialCurrentSongState: CurrentSongState = {
- playing: false,
- song: null,
-};
-
-const currentSongSlice = createSlice({
- name: 'currentSong',
- initialState: initialCurrentSongState,
- reducers: {
- loadSong: (state, action: PayloadAction