mirror of
https://github.com/navidrome/navidrome.git
synced 2026-04-28 03:19:38 +00:00
feat(ui): add EnableNowPlaying configuration (default true) (#4219)
* Add EnableNowPlaying config option * Return 501 for disabled NowPlaying * chore(tests): remove get_now_playing_route test * Disable now playing events when disabled * fix(tests): add mutex for thread-safe access to scrobble buffer Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
fcba2ba902
commit
043f79d746
14 changed files with 139 additions and 11 deletions
|
|
@ -80,6 +80,7 @@ type configOptions struct {
|
||||||
DefaultUIVolume int
|
DefaultUIVolume int
|
||||||
EnableReplayGain bool
|
EnableReplayGain bool
|
||||||
EnableCoverAnimation bool
|
EnableCoverAnimation bool
|
||||||
|
EnableNowPlaying bool
|
||||||
GATrackingID string
|
GATrackingID string
|
||||||
EnableLogRedacting bool
|
EnableLogRedacting bool
|
||||||
AuthRequestLimit int
|
AuthRequestLimit int
|
||||||
|
|
@ -491,6 +492,7 @@ func setViperDefaults() {
|
||||||
viper.SetDefault("defaultuivolume", consts.DefaultUIVolume)
|
viper.SetDefault("defaultuivolume", consts.DefaultUIVolume)
|
||||||
viper.SetDefault("enablereplaygain", true)
|
viper.SetDefault("enablereplaygain", true)
|
||||||
viper.SetDefault("enablecoveranimation", true)
|
viper.SetDefault("enablecoveranimation", true)
|
||||||
|
viper.SetDefault("enablenowplaying", true)
|
||||||
viper.SetDefault("enablesharing", false)
|
viper.SetDefault("enablesharing", false)
|
||||||
viper.SetDefault("shareurl", "")
|
viper.SetDefault("shareurl", "")
|
||||||
viper.SetDefault("defaultshareexpiration", 8760*time.Hour)
|
viper.SetDefault("defaultshareexpiration", 8760*time.Hour)
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,7 @@ var staticData = sync.OnceValue(func() insights.Data {
|
||||||
data.Config.DefaultBackgroundURLSet = conf.Server.UILoginBackgroundURL == consts.DefaultUILoginBackgroundURL
|
data.Config.DefaultBackgroundURLSet = conf.Server.UILoginBackgroundURL == consts.DefaultUILoginBackgroundURL
|
||||||
data.Config.EnableArtworkPrecache = conf.Server.EnableArtworkPrecache
|
data.Config.EnableArtworkPrecache = conf.Server.EnableArtworkPrecache
|
||||||
data.Config.EnableCoverAnimation = conf.Server.EnableCoverAnimation
|
data.Config.EnableCoverAnimation = conf.Server.EnableCoverAnimation
|
||||||
|
data.Config.EnableNowPlaying = conf.Server.EnableNowPlaying
|
||||||
data.Config.EnableDownloads = conf.Server.EnableDownloads
|
data.Config.EnableDownloads = conf.Server.EnableDownloads
|
||||||
data.Config.EnableSharing = conf.Server.EnableSharing
|
data.Config.EnableSharing = conf.Server.EnableSharing
|
||||||
data.Config.EnableStarRating = conf.Server.EnableStarRating
|
data.Config.EnableStarRating = conf.Server.EnableStarRating
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ type Data struct {
|
||||||
EnableJukebox bool `json:"enableJukebox,omitempty"`
|
EnableJukebox bool `json:"enableJukebox,omitempty"`
|
||||||
EnablePrometheus bool `json:"enablePrometheus,omitempty"`
|
EnablePrometheus bool `json:"enablePrometheus,omitempty"`
|
||||||
EnableCoverAnimation bool `json:"enableCoverAnimation,omitempty"`
|
EnableCoverAnimation bool `json:"enableCoverAnimation,omitempty"`
|
||||||
|
EnableNowPlaying bool `json:"enableNowPlaying,omitempty"`
|
||||||
SessionTimeout uint64 `json:"sessionTimeout,omitempty"`
|
SessionTimeout uint64 `json:"sessionTimeout,omitempty"`
|
||||||
SearchFullString bool `json:"searchFullString,omitempty"`
|
SearchFullString bool `json:"searchFullString,omitempty"`
|
||||||
RecentlyAddedByModTime bool `json:"recentlyAddedByModTime,omitempty"`
|
RecentlyAddedByModTime bool `json:"recentlyAddedByModTime,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
"github.com/navidrome/navidrome/consts"
|
"github.com/navidrome/navidrome/consts"
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
|
|
@ -51,10 +52,12 @@ func GetPlayTracker(ds model.DataStore, broker events.Broker) PlayTracker {
|
||||||
func newPlayTracker(ds model.DataStore, broker events.Broker) *playTracker {
|
func newPlayTracker(ds model.DataStore, broker events.Broker) *playTracker {
|
||||||
m := cache.NewSimpleCache[string, NowPlayingInfo]()
|
m := cache.NewSimpleCache[string, NowPlayingInfo]()
|
||||||
p := &playTracker{ds: ds, playMap: m, broker: broker}
|
p := &playTracker{ds: ds, playMap: m, broker: broker}
|
||||||
m.OnExpiration(func(_ string, _ NowPlayingInfo) {
|
if conf.Server.EnableNowPlaying {
|
||||||
ctx := events.BroadcastToAll(context.Background())
|
m.OnExpiration(func(_ string, _ NowPlayingInfo) {
|
||||||
broker.SendMessage(ctx, &events.NowPlayingCount{Count: m.Len()})
|
ctx := events.BroadcastToAll(context.Background())
|
||||||
})
|
broker.SendMessage(ctx, &events.NowPlayingCount{Count: m.Len()})
|
||||||
|
})
|
||||||
|
}
|
||||||
p.scrobblers = make(map[string]Scrobbler)
|
p.scrobblers = make(map[string]Scrobbler)
|
||||||
var enabled []string
|
var enabled []string
|
||||||
for name, constructor := range constructors {
|
for name, constructor := range constructors {
|
||||||
|
|
@ -89,8 +92,10 @@ func (p *playTracker) NowPlaying(ctx context.Context, playerId string, playerNam
|
||||||
|
|
||||||
ttl := time.Duration(int(mf.Duration)+5) * time.Second
|
ttl := time.Duration(int(mf.Duration)+5) * time.Second
|
||||||
_ = p.playMap.AddWithTTL(playerId, info, ttl)
|
_ = p.playMap.AddWithTTL(playerId, info, ttl)
|
||||||
ctx = events.BroadcastToAll(ctx)
|
if conf.Server.EnableNowPlaying {
|
||||||
p.broker.SendMessage(ctx, &events.NowPlayingCount{Count: p.playMap.Len()})
|
ctx = events.BroadcastToAll(ctx)
|
||||||
|
p.broker.SendMessage(ctx, &events.NowPlayingCount{Count: p.playMap.Len()})
|
||||||
|
}
|
||||||
player, _ := request.PlayerFrom(ctx)
|
player, _ := request.PlayerFrom(ctx)
|
||||||
if player.ScrobbleEnabled {
|
if player.ScrobbleEnabled {
|
||||||
p.dispatchNowPlaying(ctx, user.ID, mf)
|
p.dispatchNowPlaying(ctx, user.ID, mf)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
|
"github.com/navidrome/navidrome/conf/configtest"
|
||||||
"github.com/navidrome/navidrome/consts"
|
"github.com/navidrome/navidrome/consts"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
|
|
@ -29,6 +31,7 @@ var _ = Describe("PlayTracker", func() {
|
||||||
var fake fakeScrobbler
|
var fake fakeScrobbler
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
|
DeferCleanup(configtest.SetupConfig())
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
ctx = request.WithUser(ctx, model.User{ID: "u-1"})
|
ctx = request.WithUser(ctx, model.User{ID: "u-1"})
|
||||||
ctx = request.WithPlayer(ctx, model.Player{ScrobbleEnabled: true})
|
ctx = request.WithPlayer(ctx, model.Player{ScrobbleEnabled: true})
|
||||||
|
|
@ -113,6 +116,13 @@ var _ = Describe("PlayTracker", func() {
|
||||||
Expect(ok).To(BeTrue())
|
Expect(ok).To(BeTrue())
|
||||||
Expect(evt.Count).To(Equal(1))
|
Expect(evt.Count).To(Equal(1))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("does not send event when disabled", func() {
|
||||||
|
conf.Server.EnableNowPlaying = false
|
||||||
|
err := tracker.NowPlaying(ctx, "player-1", "player-one", "123")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(eventBroker.getEvents()).To(BeEmpty())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("GetNowPlaying", func() {
|
Describe("GetNowPlaying", func() {
|
||||||
|
|
@ -151,6 +161,14 @@ var _ = Describe("PlayTracker", func() {
|
||||||
Expect(ok).To(BeTrue())
|
Expect(ok).To(BeTrue())
|
||||||
Expect(evt.Count).To(Equal(0))
|
Expect(evt.Count).To(Equal(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("does not send event when disabled", func() {
|
||||||
|
conf.Server.EnableNowPlaying = false
|
||||||
|
tracker = newPlayTracker(ds, eventBroker)
|
||||||
|
info := NowPlayingInfo{MediaFile: track, Start: time.Now(), Username: "user"}
|
||||||
|
_ = tracker.(*playTracker).playMap.AddWithTTL("player-2", info, 10*time.Millisecond)
|
||||||
|
Consistently(func() int { return len(eventBroker.getEvents()) }).Should(Equal(0))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("Submit", func() {
|
Describe("Submit", func() {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ func serveIndex(ds model.DataStore, fs fs.FS, shareInfo *model.Share) http.Handl
|
||||||
"defaultLanguage": conf.Server.DefaultLanguage,
|
"defaultLanguage": conf.Server.DefaultLanguage,
|
||||||
"defaultUIVolume": conf.Server.DefaultUIVolume,
|
"defaultUIVolume": conf.Server.DefaultUIVolume,
|
||||||
"enableCoverAnimation": conf.Server.EnableCoverAnimation,
|
"enableCoverAnimation": conf.Server.EnableCoverAnimation,
|
||||||
|
"enableNowPlaying": conf.Server.EnableNowPlaying,
|
||||||
"gaTrackingId": conf.Server.GATrackingID,
|
"gaTrackingId": conf.Server.GATrackingID,
|
||||||
"losslessFormats": strings.ToUpper(strings.Join(mime.LosslessFormats, ",")),
|
"losslessFormats": strings.ToUpper(strings.Join(mime.LosslessFormats, ",")),
|
||||||
"devActivityPanel": conf.Server.DevActivityPanel,
|
"devActivityPanel": conf.Server.DevActivityPanel,
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,17 @@ var _ = Describe("serveIndex", func() {
|
||||||
Expect(config).To(HaveKeyWithValue("enableCoverAnimation", true))
|
Expect(config).To(HaveKeyWithValue("enableCoverAnimation", true))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("sets the enableNowPlaying", func() {
|
||||||
|
conf.Server.EnableNowPlaying = true
|
||||||
|
r := httptest.NewRequest("GET", "/index.html", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
serveIndex(ds, fs, nil)(w, r)
|
||||||
|
|
||||||
|
config := extractAppConfig(w.Body.String())
|
||||||
|
Expect(config).To(HaveKeyWithValue("enableNowPlaying", true))
|
||||||
|
})
|
||||||
|
|
||||||
It("sets the gaTrackingId", func() {
|
It("sets the gaTrackingId", func() {
|
||||||
conf.Server.GATrackingID = "UA-12345"
|
conf.Server.GATrackingID = "UA-12345"
|
||||||
r := httptest.NewRequest("GET", "/index.html", nil)
|
r := httptest.NewRequest("GET", "/index.html", nil)
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,11 @@ func (api *Router) routes() http.Handler {
|
||||||
hr(r, "getAlbumList2", api.GetAlbumList2)
|
hr(r, "getAlbumList2", api.GetAlbumList2)
|
||||||
h(r, "getStarred", api.GetStarred)
|
h(r, "getStarred", api.GetStarred)
|
||||||
h(r, "getStarred2", api.GetStarred2)
|
h(r, "getStarred2", api.GetStarred2)
|
||||||
h(r, "getNowPlaying", api.GetNowPlaying)
|
if conf.Server.EnableNowPlaying {
|
||||||
|
h(r, "getNowPlaying", api.GetNowPlaying)
|
||||||
|
} else {
|
||||||
|
h501(r, "getNowPlaying")
|
||||||
|
}
|
||||||
h(r, "getRandomSongs", api.GetRandomSongs)
|
h(r, "getRandomSongs", api.GetRandomSongs)
|
||||||
h(r, "getSongsByGenre", api.GetSongsByGenre)
|
h(r, "getSongsByGenre", api.GetSongsByGenre)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
)
|
)
|
||||||
|
|
@ -25,6 +26,7 @@ type MockDataStore struct {
|
||||||
MockedUserProps model.UserPropsRepository
|
MockedUserProps model.UserPropsRepository
|
||||||
MockedScrobbleBuffer model.ScrobbleBufferRepository
|
MockedScrobbleBuffer model.ScrobbleBufferRepository
|
||||||
MockedRadio model.RadioRepository
|
MockedRadio model.RadioRepository
|
||||||
|
scrobbleBufferMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *MockDataStore) Library(ctx context.Context) model.LibraryRepository {
|
func (db *MockDataStore) Library(ctx context.Context) model.LibraryRepository {
|
||||||
|
|
@ -193,6 +195,8 @@ func (db *MockDataStore) Player(ctx context.Context) model.PlayerRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *MockDataStore) ScrobbleBuffer(ctx context.Context) model.ScrobbleBufferRepository {
|
func (db *MockDataStore) ScrobbleBuffer(ctx context.Context) model.ScrobbleBufferRepository {
|
||||||
|
db.scrobbleBufferMu.Lock()
|
||||||
|
defer db.scrobbleBufferMu.Unlock()
|
||||||
if db.MockedScrobbleBuffer == nil {
|
if db.MockedScrobbleBuffer == nil {
|
||||||
if db.RealDS != nil {
|
if db.RealDS != nil {
|
||||||
db.MockedScrobbleBuffer = db.RealDS.ScrobbleBuffer(ctx)
|
db.MockedScrobbleBuffer = db.RealDS.ScrobbleBuffer(ctx)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
|
|
@ -9,6 +10,7 @@ import (
|
||||||
type MockedScrobbleBufferRepo struct {
|
type MockedScrobbleBufferRepo struct {
|
||||||
Error error
|
Error error
|
||||||
Data model.ScrobbleEntries
|
Data model.ScrobbleEntries
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateMockedScrobbleBufferRepo() *MockedScrobbleBufferRepo {
|
func CreateMockedScrobbleBufferRepo() *MockedScrobbleBufferRepo {
|
||||||
|
|
@ -19,6 +21,8 @@ func (m *MockedScrobbleBufferRepo) UserIDs(service string) ([]string, error) {
|
||||||
if m.Error != nil {
|
if m.Error != nil {
|
||||||
return nil, m.Error
|
return nil, m.Error
|
||||||
}
|
}
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
userIds := make(map[string]struct{})
|
userIds := make(map[string]struct{})
|
||||||
for _, e := range m.Data {
|
for _, e := range m.Data {
|
||||||
if e.Service == service {
|
if e.Service == service {
|
||||||
|
|
@ -36,6 +40,8 @@ func (m *MockedScrobbleBufferRepo) Enqueue(service, userId, mediaFileId string,
|
||||||
if m.Error != nil {
|
if m.Error != nil {
|
||||||
return m.Error
|
return m.Error
|
||||||
}
|
}
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
m.Data = append(m.Data, model.ScrobbleEntry{
|
m.Data = append(m.Data, model.ScrobbleEntry{
|
||||||
MediaFile: model.MediaFile{ID: mediaFileId},
|
MediaFile: model.MediaFile{ID: mediaFileId},
|
||||||
Service: service,
|
Service: service,
|
||||||
|
|
@ -50,6 +56,8 @@ func (m *MockedScrobbleBufferRepo) Next(service, userId string) (*model.Scrobble
|
||||||
if m.Error != nil {
|
if m.Error != nil {
|
||||||
return nil, m.Error
|
return nil, m.Error
|
||||||
}
|
}
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
for _, e := range m.Data {
|
for _, e := range m.Data {
|
||||||
if e.Service == service && e.UserID == userId {
|
if e.Service == service && e.UserID == userId {
|
||||||
return &e, nil
|
return &e, nil
|
||||||
|
|
@ -62,6 +70,8 @@ func (m *MockedScrobbleBufferRepo) Dequeue(entry *model.ScrobbleEntry) error {
|
||||||
if m.Error != nil {
|
if m.Error != nil {
|
||||||
return m.Error
|
return m.Error
|
||||||
}
|
}
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
newData := model.ScrobbleEntries{}
|
newData := model.ScrobbleEntries{}
|
||||||
for _, e := range m.Data {
|
for _, e := range m.Data {
|
||||||
if e.Service == entry.Service && e.UserID == entry.UserID && e.PlayTime == entry.PlayTime && e.MediaFile.ID == entry.MediaFile.ID {
|
if e.Service == entry.Service && e.UserID == entry.UserID && e.PlayTime == entry.PlayTime && e.MediaFile.ID == entry.MediaFile.ID {
|
||||||
|
|
@ -77,5 +87,7 @@ func (m *MockedScrobbleBufferRepo) Length() (int64, error) {
|
||||||
if m.Error != nil {
|
if m.Error != nil {
|
||||||
return 0, m.Error
|
return 0, m.Error
|
||||||
}
|
}
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
return int64(len(m.Data)), nil
|
return int64(len(m.Data)), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ const defaultConfig = {
|
||||||
listenBrainzEnabled: true,
|
listenBrainzEnabled: true,
|
||||||
enableExternalServices: true,
|
enableExternalServices: true,
|
||||||
enableCoverAnimation: true,
|
enableCoverAnimation: true,
|
||||||
|
enableNowPlaying: true,
|
||||||
devShowArtistPage: true,
|
devShowArtistPage: true,
|
||||||
devUIShowConfig: true,
|
devUIShowConfig: true,
|
||||||
enableReplayGain: true,
|
enableReplayGain: true,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { baseUrl } from './utils'
|
||||||
import throttle from 'lodash.throttle'
|
import throttle from 'lodash.throttle'
|
||||||
import { processEvent, serverDown } from './actions'
|
import { processEvent, serverDown } from './actions'
|
||||||
import { REST_URL } from './consts'
|
import { REST_URL } from './consts'
|
||||||
|
import config from './config'
|
||||||
|
|
||||||
const newEventStream = async () => {
|
const newEventStream = async () => {
|
||||||
let url = baseUrl(`${REST_URL}/events`)
|
let url = baseUrl(`${REST_URL}/events`)
|
||||||
|
|
@ -33,7 +34,9 @@ const startEventStream = async (dispatchFn) => {
|
||||||
throttledEventHandler(dispatchFn),
|
throttledEventHandler(dispatchFn),
|
||||||
)
|
)
|
||||||
newStream.addEventListener('refreshResource', eventHandler(dispatchFn))
|
newStream.addEventListener('refreshResource', eventHandler(dispatchFn))
|
||||||
newStream.addEventListener('nowPlayingCount', eventHandler(dispatchFn))
|
if (config.enableNowPlaying) {
|
||||||
|
newStream.addEventListener('nowPlayingCount', eventHandler(dispatchFn))
|
||||||
|
}
|
||||||
newStream.addEventListener('keepAlive', eventHandler(dispatchFn))
|
newStream.addEventListener('keepAlive', eventHandler(dispatchFn))
|
||||||
newStream.onerror = (e) => {
|
newStream.onerror = (e) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|
|
||||||
|
|
@ -120,9 +120,9 @@ const CustomUserMenu = ({ onClick, ...rest }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{config.devActivityPanel && permissions === 'admin' && (
|
{config.devActivityPanel &&
|
||||||
<NowPlayingPanel />
|
permissions === 'admin' &&
|
||||||
)}
|
config.enableNowPlaying && <NowPlayingPanel />}
|
||||||
{config.devActivityPanel && permissions === 'admin' && <ActivityPanel />}
|
{config.devActivityPanel && permissions === 'admin' && <ActivityPanel />}
|
||||||
<UserMenu {...rest}>
|
<UserMenu {...rest}>
|
||||||
<PersonalMenu sidebarIsOpen={true} onClick={onClick} />
|
<PersonalMenu sidebarIsOpen={true} onClick={onClick} />
|
||||||
|
|
|
||||||
65
ui/src/layout/AppBar.test.jsx
Normal file
65
ui/src/layout/AppBar.test.jsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { describe, it, beforeEach, vi } from 'vitest'
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
import { createStore, combineReducers } from 'redux'
|
||||||
|
import { activityReducer } from '../reducers'
|
||||||
|
import AppBar from './AppBar'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
|
let store
|
||||||
|
|
||||||
|
vi.mock('react-admin', () => ({
|
||||||
|
AppBar: ({ userMenu }) => <div data-testid="appbar">{userMenu}</div>,
|
||||||
|
useTranslate: () => (x) => x,
|
||||||
|
usePermissions: () => ({ permissions: 'admin' }),
|
||||||
|
getResources: () => [],
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('./NowPlayingPanel', () => ({
|
||||||
|
default: () => <div data-testid="now-playing-panel" />,
|
||||||
|
}))
|
||||||
|
vi.mock('./ActivityPanel', () => ({
|
||||||
|
default: () => <div data-testid="activity-panel" />,
|
||||||
|
}))
|
||||||
|
vi.mock('./PersonalMenu', () => ({
|
||||||
|
default: () => <div />,
|
||||||
|
}))
|
||||||
|
vi.mock('./UserMenu', () => ({
|
||||||
|
default: ({ children }) => <div>{children}</div>,
|
||||||
|
}))
|
||||||
|
vi.mock('../dialogs/Dialogs', () => ({
|
||||||
|
Dialogs: () => <div />,
|
||||||
|
}))
|
||||||
|
vi.mock('../dialogs', () => ({
|
||||||
|
AboutDialog: () => <div />,
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('<AppBar />', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
config.devActivityPanel = true
|
||||||
|
config.enableNowPlaying = true
|
||||||
|
store = createStore(combineReducers({ activity: activityReducer }), {
|
||||||
|
activity: { nowPlayingCount: 0 },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders NowPlayingPanel when enabled', () => {
|
||||||
|
render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<AppBar />
|
||||||
|
</Provider>,
|
||||||
|
)
|
||||||
|
expect(screen.getByTestId('now-playing-panel')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides NowPlayingPanel when disabled', () => {
|
||||||
|
config.enableNowPlaying = false
|
||||||
|
render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<AppBar />
|
||||||
|
</Provider>,
|
||||||
|
)
|
||||||
|
expect(screen.queryByTestId('now-playing-panel')).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue