Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions src/stores/__tests__/sounds.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import BrowserStorage from '../../services/BrowserStorage.js'
import { getTalkConfig } from '../../services/CapabilitiesManager.ts'

vi.mock('@nextcloud/auth', () => ({
getCurrentUser: vi.fn(),
}))
vi.mock('@nextcloud/axios', () => ({
default: {
post: vi.fn(() => Promise.resolve()),
},
}))
vi.mock('../../services/BrowserStorage.js', () => ({
default: {
getItem: vi.fn(),
setItem: vi.fn(),
},
}))
vi.mock('../../services/CapabilitiesManager.ts', () => ({
getTalkConfig: vi.fn(),
}))

/**
* The initial value is computed when the module is loaded, so load it fresh for every test
*/
async function loadSoundsStore() {
vi.resetModules()
const { useSoundsStore } = await import('../sounds.js')
setActivePinia(createPinia())
return useSoundsStore()
}

describe('soundsStore', () => {
beforeEach(() => {
vi.clearAllMocks()
getCurrentUser.mockReturnValue({ uid: 'alice' })
getTalkConfig.mockReturnValue(undefined)
BrowserStorage.getItem.mockReturnValue(null)
})

describe('initial value for users', () => {
it('takes the value from the capabilities on Talk 24+', async () => {
getTalkConfig.mockReturnValue(false)
const store = await loadSoundsStore()
expect(store.shouldPlaySounds).toBe(false)
expect(getTalkConfig).toHaveBeenCalledWith('local', 'call', 'play-sounds')
})

it('prefers the capabilities over browser storage', async () => {
getTalkConfig.mockReturnValue(true)
BrowserStorage.getItem.mockReturnValue('no')
const store = await loadSoundsStore()
expect(store.shouldPlaySounds).toBe(true)
})

it('falls back to browser storage when the server has no capability', async () => {
BrowserStorage.getItem.mockReturnValue('no')
const store = await loadSoundsStore()
expect(store.shouldPlaySounds).toBe(false)
})

it('defaults to enabled without capability or storage', async () => {
const store = await loadSoundsStore()
expect(store.shouldPlaySounds).toBe(true)
})
})

describe('initial value for guests', () => {
beforeEach(() => {
getCurrentUser.mockReturnValue(null)
})

it('prefers browser storage over the capabilities', async () => {
getTalkConfig.mockReturnValue(true)
BrowserStorage.getItem.mockReturnValue('no')
const store = await loadSoundsStore()
expect(store.shouldPlaySounds).toBe(false)
})

it('takes the value from the capabilities without storage', async () => {
getTalkConfig.mockReturnValue(false)
const store = await loadSoundsStore()
expect(store.shouldPlaySounds).toBe(false)
})

it('defaults to enabled on older servers', async () => {
const store = await loadSoundsStore()
expect(store.shouldPlaySounds).toBe(true)
})
})

describe('setShouldPlaySounds', () => {
it('saves on the server only when the capability exists', async () => {
getTalkConfig.mockReturnValue(true)
const store = await loadSoundsStore()
await store.setShouldPlaySounds(false)
expect(axios.post).toHaveBeenCalledWith(
generateOcsUrl('apps/spreed/api/v1/settings/user'),
{ key: 'play_sounds', value: 'no' },
)
expect(BrowserStorage.setItem).not.toHaveBeenCalled()
expect(store.shouldPlaySounds).toBe(false)
})

it('saves to browser storage instead on older servers, since they cannot hand it back', async () => {
const store = await loadSoundsStore()
await store.setShouldPlaySounds(false)
expect(axios.post).not.toHaveBeenCalled()
expect(BrowserStorage.setItem).toHaveBeenCalledWith('play_sounds', 'no')
expect(store.shouldPlaySounds).toBe(false)
})

it('saves guests to browser storage', async () => {
getCurrentUser.mockReturnValue(null)
const store = await loadSoundsStore()
await store.setShouldPlaySounds(true)
expect(axios.post).not.toHaveBeenCalled()
expect(BrowserStorage.setItem).toHaveBeenCalledWith('play_sounds', 'yes')
})
})
})
32 changes: 22 additions & 10 deletions src/stores/sounds.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,32 @@ import { getTalkConfig } from '../services/CapabilitiesManager.ts'
import { setPlaySounds } from '../services/settingsService.ts'

const hasUserAccount = Boolean(getCurrentUser()?.uid)
const playSoundsCapability = getTalkConfig('local', 'call', 'play-sounds')
const hasPlaySoundsCapability = playSoundsCapability !== undefined

/**
* Get play sounds option (from server for user or from browser storage for guest)
* Get play sounds option. A guest keeps whatever this browser remembered, otherwise the
* capability decides. Servers before Talk 24 don't hand the value out at all, so fall back
* to the browser and then to enabled.
*
* @return {boolean}
*/
let shouldPlaySounds = false
if (hasUserAccount) {
shouldPlaySounds = getTalkConfig('local', 'call', 'play-sounds')
} else {
if (BrowserStorage.getItem('play_sounds')) {
shouldPlaySounds = BrowserStorage.getItem('play_sounds') !== 'no'
} else {
shouldPlaySounds = getTalkConfig('local', 'call', 'play-sounds')
function getInitialShouldPlaySounds() {
const fromStorage = BrowserStorage.getItem('play_sounds')

if (!hasUserAccount && fromStorage) {
return fromStorage !== 'no'
}

if (hasPlaySoundsCapability) {
return playSoundsCapability
}

return fromStorage ? fromStorage !== 'no' : true
}

const shouldPlaySounds = getInitialShouldPlaySounds()

/**
* Preferred version is the .ogg, with .flac fallback if .ogg is not supported (Safari)
*/
Expand Down Expand Up @@ -55,7 +67,7 @@ export const useSoundsStore = defineStore('sounds', {
* @param {boolean} value whether sounds should be played
*/
async setShouldPlaySounds(value) {
await setPlaySounds(hasUserAccount, value ? 'yes' : 'no')
await setPlaySounds(hasUserAccount && hasPlaySoundsCapability, value ? 'yes' : 'no')
this.shouldPlaySounds = value
},

Expand Down
Loading