LCOV - code coverage report
Current view: top level - contexts/SongContext.jsx - SongContext.jsx (source / functions) Hit Total Coverage
Test: feature-lcov.info Lines: 159 476 33.4 %
Date: 2025-11-22 00:01:46 Functions: 3 7 42.9 %

          Line data    Source code
       1             : // Filepath: Melodex/melodex-front-end/src/contexts/SongContext.jsx
       2           1 : import React, { createContext, useState, useCallback, useContext, useEffect, useRef } from 'react';
       3           1 : import { useUserContext } from './UserContext';
       4             : 
       5           1 : const SongContext = createContext();
       6             : 
       7           1 : export const useSongContext = () => {
       8         251 :   const context = useContext(SongContext);
       9         251 :   if (!context) throw new Error('useSongContext must be used within a SongProvider');
      10         251 :   return context;
      11         251 : };
      12             : 
      13           1 : export const SongProvider = ({ children }) => {
      14          59 :   const { userID } = useUserContext();
      15          59 :   console.log('SongProvider: userID from UserContext:', userID);
      16             : 
      17          59 :   const [songList, setSongList] = useState([]);
      18          59 :   const [songBuffer, setSongBuffer] = useState([]);
      19          59 :   const [currentPair, setCurrentPair] = useState([]);
      20          59 :   const [rankedSongs, setRankedSongs] = useState([]);
      21          59 :   const [loading, setLoading] = useState(false);
      22          59 :   const [mode, setMode] = useState('new');
      23          59 :   const [selectedGenre, setSelectedGenre] = useState('any');
      24             : 
      25             :   // These are controlled by /rank apply
      26          59 :   const [lastFilters, setLastFilters] = useState({ genre: 'any', subgenre: 'any', decade: 'all decades' });
      27          59 :   const [filtersApplied, setFiltersApplied] = useState(false);
      28             : 
      29          59 :   const [isBackgroundFetching, setIsBackgroundFetching] = useState(false);
      30          59 :   const [isRankPageActive, setIsRankPageActive] = useState(false);
      31          59 :   const [contextUserID, setContextUserID] = useState(null);
      32             : 
      33             :   // Guards/refs
      34          59 :   const inFlightRef = useRef(false);
      35          59 :   const lastBgFetchAtRef = useRef(0);
      36          59 :   const unmountedRef = useRef(false);
      37             : 
      38             :   // For accurate totals during bursts:
      39          59 :   const listLenRef = useRef(0);
      40          59 :   const bufferLenRef = useRef(0);
      41          59 :   useEffect(() => { listLenRef.current = songList.length; }, [songList.length]);
      42          59 :   useEffect(() => { bufferLenRef.current = songBuffer.length; }, [songBuffer.length]);
      43             : 
      44             :   // Prefetch tuning (feel free to tweak)
      45          59 :   const PREFETCH_TARGET = 30;       // aim to keep list+buffer around this
      46          59 :   const LOW_WATERMARK   = 10;       // when list drops below this, refill from buffer
      47          59 :   const PREFETCH_COOLDOWN_MS = 6000;
      48          59 :   const MAX_PAGES_PER_BURST = 2;    // smaller burst
      49          59 :   const MAX_BUFFER = 40;            // hard cap on buffer size
      50             : 
      51          59 :   useEffect(() => {
      52          12 :     unmountedRef.current = false;
      53          12 :     return () => { unmountedRef.current = true; };
      54          59 :   }, []);
      55             : 
      56          59 :   useEffect(() => {
      57          19 :     console.log('SongProvider useEffect: Setting contextUserID to', userID);
      58          19 :     setContextUserID(userID);
      59          59 :   }, [userID]);
      60             : 
      61             :   // ---- Burst background prefetch (only /rank after filters applied) ----
      62          59 :   useEffect(() => {
      63          24 :     const runBurstPrefetch = async () => {
      64          24 :       const totalAvailableStart = songList.length + songBuffer.length;
      65          24 :       const now = Date.now();
      66          24 :       const cooledDown = now - lastBgFetchAtRef.current >= PREFETCH_COOLDOWN_MS;
      67             : 
      68          24 :       const canFetch =
      69          24 :         !!contextUserID &&
      70          12 :         mode === 'new' &&
      71          12 :         isRankPageActive &&
      72           0 :         filtersApplied &&
      73           0 :         cooledDown &&
      74           0 :         !inFlightRef.current &&
      75           0 :         !isBackgroundFetching &&
      76           0 :         totalAvailableStart > 0 &&                  // <-- wait for *some* songs to exist (avoids overlap with initial fetch)
      77           0 :         totalAvailableStart < PREFETCH_TARGET;
      78             : 
      79          24 :       if (!canFetch) {
      80          24 :         console.log('Background fetch skipped:', {
      81          24 :           contextUserID,
      82          24 :           mode,
      83          24 :           isRankPageActive,
      84          24 :           filtersApplied,
      85          24 :           isBackgroundFetching,
      86          24 :           cooledDown,
      87          24 :           totalAvailable: totalAvailableStart,
      88          24 :           songListLength: songList.length,
      89          24 :           songBufferLength: songBuffer.length
      90          24 :         });
      91          24 :         return;
      92          24 :       }
      93             : 
      94           0 :       console.log('Triggering background fetch BURST (start totalAvailable:', totalAvailableStart, ')');
      95           0 :       inFlightRef.current = true;
      96           0 :       setIsBackgroundFetching(true);
      97           0 :       lastBgFetchAtRef.current = now;
      98             : 
      99           0 :       try {
     100           0 :         let pagesFetched = 0;
     101             : 
     102           0 :         while (pagesFetched < MAX_PAGES_PER_BURST) {
     103             :           // live totals from refs so we count changes during the burst
     104           0 :           const liveTotal = listLenRef.current + bufferLenRef.current;
     105           0 :           if (liveTotal >= PREFETCH_TARGET) break;
     106             : 
     107           0 :           const newSongs = await generateNewSongs(lastFilters, true);
     108           0 :           pagesFetched += 1;
     109             : 
     110           0 :           if (!newSongs || newSongs.length === 0) {
     111           0 :             console.warn('Burst prefetch: page returned 0 songs; stopping burst.');
     112           0 :             break;
     113           0 :           }
     114             : 
     115           0 :           setSongBuffer(prev => {
     116             :             // Append but enforce hard cap
     117           0 :             const updated = [...prev, ...newSongs];
     118           0 :             let final = updated;
     119           0 :             if (updated.length > MAX_BUFFER) {
     120           0 :               final = updated.slice(0, MAX_BUFFER);
     121           0 :             }
     122           0 :             console.log(`Burst page ${pagesFetched}: +${newSongs.length} songs -> buffer size now: ${final.length}`);
     123           0 :             return final;
     124           0 :           });
     125           0 :         }
     126          24 :       } catch (err) {
     127           0 :         console.error('Burst prefetch error:', err);
     128           0 :       } finally {
     129           0 :         inFlightRef.current = false;
     130           0 :         if (!unmountedRef.current) setIsBackgroundFetching(false);
     131           0 :         console.log('Background fetch BURST complete.');
     132           0 :       }
     133          24 :     };
     134             : 
     135          24 :     runBurstPrefetch();
     136          59 :   }, [
     137          59 :     songList.length,          // only lengths (not arrays) to avoid identity churn
     138          59 :     songBuffer.length,
     139          59 :     contextUserID,
     140          59 :     mode,
     141          59 :     isRankPageActive,
     142          59 :     filtersApplied
     143          59 :   ]);
     144             : 
     145             :   // ---- Refill visible list from buffer when it gets low ----
     146          59 :   useEffect(() => {
     147          24 :     if (songList.length < LOW_WATERMARK && songBuffer.length > 0) {
     148           0 :       const want = LOW_WATERMARK * 2; // ~20 at a time
     149           0 :       const batchSize = Math.min(want, songBuffer.length);
     150           0 :       const newSongs = songBuffer.slice(0, batchSize);
     151           0 :       setSongList(prevList => [...prevList, ...newSongs]);
     152           0 :       setSongBuffer(prevBuffer => prevBuffer.slice(batchSize));
     153           0 :       console.log('Replenished songList from buffer:', newSongs.length, 'list size now:', (songList.length + newSongs.length));
     154           0 :     } else if (
     155          24 :       songList.length === 0 &&
     156          24 :       songBuffer.length === 0 &&
     157          24 :       mode === 'new' &&
     158          24 :       contextUserID &&
     159          12 :       isRankPageActive &&
     160           0 :       filtersApplied
     161          24 :     ) {
     162           0 :       console.log('Song list and buffer empty after filters; fetching initial songs');
     163           0 :       generateNewSongs(lastFilters).then(newSongs => {
     164           0 :         if (newSongs.length > 0) {
     165           0 :           setSongList(newSongs);
     166           0 :           console.log('Initial fetch (post-apply):', newSongs.length);
     167           0 :         } else {
     168           0 :           console.warn('Initial fetch returned no songs');
     169           0 :         }
     170           0 :       });
     171           0 :     }
     172          59 :   }, [songList.length, songBuffer.length, mode, contextUserID, lastFilters, isRankPageActive, filtersApplied]);
     173             : 
     174          59 :   const getNextPair = useCallback((songsToUse = songList) => {
     175           0 :     if (!Array.isArray(songsToUse)) {
     176           0 :       console.error('getNextPair: songsToUse is not an array', songsToUse);
     177           0 :       setCurrentPair([]);
     178           0 :       return;
     179           0 :     }
     180           0 :     const validSongs = songsToUse.filter(song => song && song.deezerID);
     181           0 :     console.log('getNextPair: Valid songs available:', validSongs.length, validSongs);
     182           0 :     if (validSongs.length < 2) {
     183           0 :       console.log('getNextPair: Not enough valid songs, currentPair set to empty');
     184           0 :       setCurrentPair([]);
     185           0 :       return;
     186           0 :     }
     187           0 :     const song1 = validSongs[0];
     188           0 :     const song2 = validSongs.find(song => String(song.deezerID) !== String(song1.deezerID));
     189           0 :     if (!song2) {
     190           0 :       console.error('getNextPair: Could not find a second song');
     191           0 :       setCurrentPair([]);
     192           0 :       return;
     193           0 :     }
     194           0 :     const newPair = [song1, song2];
     195           0 :     setCurrentPair(newPair);
     196           0 :     setSongList(validSongs.filter(song => String(song.deezerID) !== String(song1.deezerID) && String(song.deezerID) !== String(song2.deezerID)));
     197           0 :     console.log('getNextPair: New pair set:', newPair);
     198          59 :   }, [songList]);
     199             : 
     200          59 :   const generateNewSongs = async (filters = lastFilters ?? { genre: 'any', subgenre: 'any', decade: 'all decades' }, isBackground = false) => {
     201           0 :     if (!userID) return [];
     202           0 :     if (!isBackground) {
     203           0 :       setLoading(true);
     204           0 :       console.log('Loading set to true');
     205           0 :     }
     206           0 :     try {
     207           0 :       const controller = new AbortController();
     208           0 :       const timeoutId = setTimeout(() => {
     209           0 :         controller.abort();
     210           0 :         console.log('generateNewSongs fetch timed out');
     211           0 :       }, 30000);
     212             : 
     213           0 :       const url = `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api'}/user-songs/new`;
     214           0 :       console.log('generateNewSongs filters:', filters);
     215           0 :       const response = await fetch(url, {
     216           0 :         method: 'POST',
     217           0 :         body: JSON.stringify({ userID, ...filters }),
     218           0 :         headers: { 'Content-Type': 'application/json' },
     219           0 :         signal: controller.signal,
     220           0 :       });
     221             : 
     222           0 :       clearTimeout(timeoutId);
     223           0 :       if (!response.ok) {
     224           0 :         const errorText = await response.text();
     225           0 :         throw new Error(`Failed to fetch new songs: ${response.status} ${errorText}`);
     226           0 :       }
     227           0 :       const songs = await response.json();
     228           0 :       return songs;
     229           0 :     } catch (error) {
     230           0 :       console.error('Failed to generate new songs:', error);
     231           0 :       return [];
     232           0 :     } finally {
     233           0 :       if (!isBackground) {
     234           0 :         setLoading(false);
     235           0 :         console.log('Loading set to false');
     236           0 :       }
     237           0 :     }
     238           0 :   };
     239             : 
     240          59 :   const fetchReRankingData = async (genre = selectedGenre, subgenre = 'any', setContext = true) => {
     241           0 :     if (!contextUserID) {
     242           0 :       console.error('No userID available for fetchReRankingData');
     243           0 :       return [];
     244           0 :     }
     245           0 :     setLoading(true);
     246           0 :     console.log('Loading set to true');
     247           0 :     try {
     248           0 :       console.log('fetchReRankingData with genre:', genre, 'subgenre:', subgenre);
     249           0 :       const payload = { userID: contextUserID };
     250           0 :       if (subgenre !== 'any') {
     251           0 :         payload.subgenre = subgenre;
     252           0 :         if (genre !== 'any') payload.genre = genre;
     253           0 :       } else if (genre !== 'any') {
     254           0 :         payload.genre = genre;
     255           0 :       }
     256           0 :       console.log('fetchReRankingData payload:', payload);
     257           0 :       const url = `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api'}/user-songs/rerank`;
     258           0 :       const response = await fetch(url, {
     259           0 :         method: 'POST',
     260           0 :         headers: { 'Content-Type': 'application/json' },
     261           0 :         body: JSON.stringify(payload),
     262           0 :       });
     263           0 :       if (!response.ok) throw new Error('Failed to fetch re-ranking data');
     264           0 :       const reRankSongs = await response.json();
     265           0 :       console.log('fetchReRankingData: Retrieved songs:', reRankSongs);
     266           0 :       if (setContext) {
     267           0 :         setSongList(reRankSongs);
     268           0 :         getNextPair(reRankSongs);
     269           0 :       }
     270           0 :       return reRankSongs;
     271           0 :     } catch (error) {
     272           0 :       console.error('Failed to fetch re-ranking data:', error);
     273           0 :       return [];
     274           0 :     } finally {
     275           0 :       setLoading(false);
     276           0 :       console.log('Loading set to false');
     277           0 :     }
     278           0 :   };
     279             : 
     280          59 :   const fetchRankedSongs = useCallback(async ({ userID: fetchUserID, genre = selectedGenre, subgenre = 'any' }) => {
     281          26 :     const idToUse = fetchUserID || contextUserID;
     282          26 :     console.log('fetchRankedSongs called with userID:', idToUse, 'genre:', genre, 'subgenre:', subgenre);
     283          26 :     if (!idToUse) {
     284           0 :       console.error('No userID available for fetchRankedSongs');
     285           0 :       setRankedSongs([]);
     286           0 :       return [];
     287           0 :     }
     288          26 :     setLoading(true);
     289          26 :     console.log('Loading set to true');
     290          26 :     const url = `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api'}/user-songs/ranked`;
     291          26 :     console.log('Fetching ranked songs from:', url);
     292             : 
     293          26 :     try {
     294          26 :       const payload = { userID: idToUse };
     295          26 :       if (genre !== 'any') payload.genre = genre;
     296          26 :       if (subgenre !== 'any') payload.subgenre = subgenre;
     297             : 
     298          26 :       const response = await fetch(url, {
     299          26 :         method: 'POST',
     300          26 :         headers: { 'Content-Type': 'application/json' },
     301          26 :         body: JSON.stringify(payload),
     302          26 :       });
     303             : 
     304          26 :       if (!response.ok) throw new Error('Failed to fetch ranked songs');
     305             : 
     306          26 :       const text = await response.text();
     307          26 :       try {
     308          26 :         const ranked = JSON.parse(text);
     309          26 :         setRankedSongs(ranked);
     310          26 :         return ranked;
     311          26 :       } catch {
     312           0 :         setRankedSongs([]);
     313           0 :         return [];
     314           0 :       }
     315          26 :     } catch (error) {
     316           0 :       console.error('Failed to fetch ranked songs:', error);
     317           0 :       setRankedSongs([]);
     318           0 :       return [];
     319          26 :     } finally {
     320          26 :       setLoading(false);
     321          26 :       console.log('Loading set to false');
     322          26 :     }
     323          59 :   }, [contextUserID, selectedGenre]);
     324             : 
     325          59 :   const selectSong = async (winnerId, loserId, resetProcessing) => {
     326           0 :     if (!contextUserID) {
     327           0 :       console.error('No userID available for selectSong');
     328           0 :       resetProcessing?.();
     329           0 :       return;
     330           0 :     }
     331           0 :     setLoading(true);
     332           0 :     console.log('Loading set to true');
     333           0 :     try {
     334           0 :       const winnerSong = currentPair.find(s => s.deezerID.toString() === winnerId.toString());
     335           0 :       const loserSong = currentPair.find(s => s.deezerID.toString() === loserId.toString());
     336           0 :       if (!winnerSong || !loserSong) {
     337           0 :         console.error('Winner or loser song not found in currentPair', { winnerId, loserId, currentPair });
     338           0 :         resetProcessing?.();
     339           0 :         return;
     340           0 :       }
     341             : 
     342           0 :       const payload = {
     343           0 :         userID,
     344           0 :         deezerID: winnerSong.deezerID,
     345           0 :         opponentDeezerID: loserSong.deezerID,
     346           0 :         result: 'win',
     347           0 :         winnerSongName: winnerSong.songName,
     348           0 :         winnerArtist: winnerSong.artist,
     349           0 :         winnerGenre: winnerSong.genre,
     350           0 :         winnerSubgenre: winnerSong.subgenre || null,
     351           0 :         winnerDecade: winnerSong.decade || null,
     352           0 :         winnerAlbumCover: winnerSong.albumCover,
     353           0 :         winnerPreviewURL: winnerSong.previewURL,
     354           0 :         loserSongName: loserSong.songName,
     355           0 :         loserArtist: loserSong.artist,
     356           0 :         loserGenre: loserSong.genre,
     357           0 :         loserSubgenre: loserSong.subgenre || null,
     358           0 :         loserDecade: loserSong.decade || null,
     359           0 :         loserAlbumCover: loserSong.albumCover,
     360           0 :         loserPreviewURL: loserSong.previewURL,
     361           0 :       };
     362             : 
     363           0 :       const url = `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api'}/user-songs/upsert`;
     364           0 :       const response = await fetch(url, {
     365           0 :         method: 'POST',
     366           0 :         headers: { 'Content-Type': 'application/json' },
     367           0 :         body: JSON.stringify(payload),
     368           0 :       });
     369             : 
     370           0 :       if (!response.ok) throw new Error('Failed to update song ratings');
     371             : 
     372           0 :       const { newRatingA, newRatingB } = await response.json();
     373             : 
     374           0 :       const updatedList = songList.filter(song => String(song.deezerID) !== String(winnerId) && String(song.deezerID) !== String(loserId));
     375           0 :       setSongList(updatedList);
     376           0 :       setRankedSongs(prev => ([
     377           0 :         ...prev.filter(s =>
     378           0 :           String(s.deezerID) !== String(winnerSong.deezerID) &&
     379           0 :           String(s.deezerID) !== String(loserSong.deezerID)
     380           0 :         ),
     381           0 :         { ...winnerSong, ranking: newRatingA },
     382           0 :         { ...loserSong, ranking: newRatingB }
     383           0 :       ]));
     384             : 
     385           0 :       if (mode === 'new') {
     386           0 :         getNextPair(updatedList);
     387           0 :       } else if (mode === 'rerank') {
     388           0 :         const newPair = await fetchReRankingData();
     389           0 :         setCurrentPair(newPair.length >= 2 ? newPair : []);
     390           0 :         setSongList([]);
     391           0 :       }
     392           0 :     } catch (error) {
     393           0 :       console.error('Failed to select song:', error.message);
     394           0 :     } finally {
     395           0 :       setLoading(false);
     396           0 :       console.log('Loading set to false');
     397           0 :       resetProcessing?.();
     398           0 :     }
     399           0 :   };
     400             : 
     401          59 :   const skipSong = async (songId, resetProcessing) => {
     402           0 :     if (!contextUserID) {
     403           0 :       console.error('No userID available for skipSong');
     404           0 :       resetProcessing?.();
     405           0 :       return;
     406           0 :     }
     407           0 :     setLoading(true);
     408           0 :     console.log('Loading set to true');
     409           0 :     try {
     410           0 :       const skippedSong = currentPair.find(s => s.deezerID.toString() === songId.toString());
     411           0 :       const keptSong = currentPair.find(s => s.deezerID.toString() !== songId.toString());
     412           0 :       if (!skippedSong || !keptSong) {
     413           0 :         console.error('Skipped song or kept song not found in currentPair:', { songId, currentPair });
     414           0 :         resetProcessing?.();
     415           0 :         return;
     416           0 :       }
     417             : 
     418           0 :       const payload = {
     419           0 :         userID,
     420           0 :         deezerID: songId,
     421           0 :         ranking: null,
     422           0 :         skipped: true,
     423           0 :         songName: skippedSong.songName || 'Unknown Song',
     424           0 :         artist: skippedSong.artist || 'Unknown Artist',
     425           0 :         genre: skippedSong.genre || 'unknown',
     426           0 :         subgenre: skippedSong.subgenre || null,
     427           0 :         decade: skippedSong.decade || null,
     428           0 :         albumCover: skippedSong.albumCover || '',
     429           0 :         previewURL: skippedSong.previewURL || '',
     430           0 :       };
     431             : 
     432           0 :       const url = `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api'}/user-songs/upsert`;
     433           0 :       const response = await fetch(url, {
     434           0 :         method: 'POST',
     435           0 :         headers: { 'Content-Type': 'application/json' },
     436           0 :         body: JSON.stringify(payload),
     437           0 :       });
     438           0 :       if (!response.ok) throw new Error('Failed to skip song');
     439             : 
     440           0 :       if (mode === 'rerank') {
     441           0 :         const reRankSongs = await fetchReRankingData();
     442           0 :         if (reRankSongs.length > 0) {
     443           0 :           const newSong = reRankSongs.find(s => String(s.deezerID) !== String(keptSong.deezerID));
     444           0 :           setCurrentPair(newSong ? [keptSong, newSong] : [keptSong]);
     445           0 :         } else {
     446           0 :           setCurrentPair([]);
     447           0 :         }
     448           0 :       } else {
     449           0 :         if (songList.length > 0) {
     450           0 :           const nextSong = songList[0];
     451           0 :           setCurrentPair([nextSong, keptSong]);
     452           0 :           setSongList(songList.slice(1));
     453           0 :         } else {
     454           0 :           setCurrentPair([]);
     455           0 :         }
     456           0 :       }
     457           0 :     } catch (error) {
     458           0 :       console.error('Failed to skip song:', error.message);
     459           0 :       setCurrentPair([]);
     460           0 :     } finally {
     461           0 :       setLoading(false);
     462           0 :       console.log('Loading set to false');
     463           0 :       resetProcessing?.();
     464           0 :     }
     465           0 :   };
     466             : 
     467          59 :   const refreshPair = useCallback(async (resetProcessing) => {
     468           0 :     if (!contextUserID) {
     469           0 :       console.error('No userID available for refreshPair');
     470           0 :       resetProcessing?.();
     471           0 :       return;
     472           0 :     }
     473           0 :     setLoading(true);
     474           0 :     console.log('Loading set to true');
     475           0 :     try {
     476           0 :       if (mode === 'new' && currentPair.length === 2) {
     477           0 :         await Promise.all([
     478           0 :           skipSong(currentPair[0].deezerID),
     479           0 :           skipSong(currentPair[1].deezerID),
     480           0 :         ]);
     481           0 :         getNextPair(songList);
     482           0 :       } else if (mode === 'rerank') {
     483           0 :         const newPair = await fetchReRankingData();
     484           0 :         setCurrentPair(newPair.length >= 2 ? newPair : []);
     485           0 :       }
     486           0 :     } catch (error) {
     487           0 :       console.error('Failed to refresh pair:', error);
     488           0 :       setCurrentPair([]);
     489           0 :     } finally {
     490           0 :       setLoading(false);
     491           0 :       console.log('Loading set to false');
     492           0 :       resetProcessing?.();
     493           0 :     }
     494          59 :   }, [mode, currentPair, songList, skipSong, fetchReRankingData]);
     495             : 
     496          59 :   useEffect(() => {
     497          12 :     setCurrentPair([]);
     498          59 :   }, [mode]);
     499             : 
     500          59 :   return (
     501          59 :     <SongContext.Provider
     502          59 :       value={{
     503          59 :         songList,
     504          59 :         setSongList,
     505          59 :         songBuffer,
     506          59 :         setSongBuffer,
     507          59 :         currentPair,
     508          59 :         setCurrentPair,
     509          59 :         rankedSongs,
     510          59 :         loading,
     511          59 :         setLoading,
     512          59 :         mode,
     513          59 :         setMode,
     514          59 :         getNextPair,
     515          59 :         generateNewSongs,
     516          59 :         fetchReRankingData,
     517          59 :         selectSong,
     518          59 :         skipSong,
     519          59 :         fetchRankedSongs,
     520          59 :         refreshPair,
     521          59 :         selectedGenre,
     522          59 :         setSelectedGenre,
     523          59 :         userID: contextUserID,
     524          59 :         setIsRankPageActive,
     525          59 :         setLastFilters,
     526          59 :         setFiltersApplied
     527          59 :       }}
     528             :     >
     529          59 :       {children}
     530          59 :     </SongContext.Provider>
     531             :   );
     532          59 : };
     533             : 
     534           1 : export default SongProvider;

Generated by: LCOV version 1.15.alpha0w