mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
feat(test): add tests for multiple functions
This commit is contained in:
parent
179faefeed
commit
891cabaeb8
37 changed files with 311 additions and 195 deletions
|
@ -38,7 +38,7 @@ class SongController extends Controller
|
|||
$this->songRepository->getForListing(
|
||||
sortColumn: $request->sort ?: 'songs.title',
|
||||
sortDirection: $request->order ?: 'asc',
|
||||
ownSongsOnly: (bool) $request->ownSongsOnly,
|
||||
ownSongsOnly: $request->boolean('own_songs_only'),
|
||||
scopedUser: $this->user
|
||||
)
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace App\Http\Requests\API;
|
|||
/**
|
||||
* @property-read string $order
|
||||
* @property-read string $sort
|
||||
* @property-read boolean|string|integer $ownSongsOnly
|
||||
* @property-read boolean|string|integer $own_songs_only
|
||||
*/
|
||||
class SongListRequest extends Request
|
||||
{
|
||||
|
|
|
@ -52,21 +52,22 @@ export default abstract class UnitTestCase {
|
|||
|
||||
protected afterEach (cb?: Closure) {
|
||||
afterEach(() => {
|
||||
isMobile.any = false
|
||||
commonStore.state.song_length = 10
|
||||
cleanup()
|
||||
this.restoreAllMocks()
|
||||
isMobile.any = false
|
||||
this.disablePlusEdition()
|
||||
cb && cb()
|
||||
})
|
||||
}
|
||||
|
||||
protected actingAs (user?: User) {
|
||||
protected be (user?: User) {
|
||||
userStore.state.current = user || factory<User>('user')
|
||||
return this
|
||||
}
|
||||
|
||||
protected actingAsAdmin () {
|
||||
return this.actingAs(factory.states('admin')<User>('user'))
|
||||
protected beAdmin () {
|
||||
return this.be(factory.states('admin')<User>('user'))
|
||||
}
|
||||
|
||||
protected mock<T, M extends MethodOf<Required<T>>> (obj: T, methodName: M, implementation?: any) {
|
||||
|
@ -137,6 +138,26 @@ export default abstract class UnitTestCase {
|
|||
return options
|
||||
}
|
||||
|
||||
protected enablePlusEdition () {
|
||||
commonStore.state.koel_plus = {
|
||||
active: true,
|
||||
short_key: '****-XXXX',
|
||||
customer_name: 'John Doe',
|
||||
customer_email: 'Koel Plus',
|
||||
product_id: 'koel-plus',
|
||||
}
|
||||
}
|
||||
|
||||
protected disablePlusEdition () {
|
||||
commonStore.state.koel_plus = {
|
||||
active: false,
|
||||
short_key: '',
|
||||
customer_name: '',
|
||||
customer_email: '',
|
||||
product_id: '',
|
||||
}
|
||||
}
|
||||
|
||||
protected stub (testId = 'stub') {
|
||||
return defineComponent({
|
||||
template: `<br data-testid="${testId}"/>`
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
declare global {
|
||||
interface Window {
|
||||
BASE_URL: string;
|
||||
}
|
||||
}
|
||||
|
||||
import vueSnapshotSerializer from 'jest-serializer-vue'
|
||||
import { expect, vi } from 'vitest'
|
||||
import Axios from 'axios'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
BASE_URL: string;
|
||||
createLemonSqueezy: () => void;
|
||||
}
|
||||
|
||||
interface LemonSqueezy {
|
||||
Url: {
|
||||
Open: () => void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect.addSnapshotSerializer(vueSnapshotSerializer)
|
||||
|
||||
global.ResizeObserver = global.ResizeObserver ||
|
||||
|
@ -17,6 +24,13 @@ global.ResizeObserver = global.ResizeObserver ||
|
|||
unobserve: vi.fn()
|
||||
}))
|
||||
|
||||
|
||||
global.LemonSqueezy = {
|
||||
Url: {
|
||||
Open: vi.fn()
|
||||
}
|
||||
}
|
||||
|
||||
HTMLMediaElement.prototype.load = vi.fn()
|
||||
HTMLMediaElement.prototype.play = vi.fn()
|
||||
HTMLMediaElement.prototype.pause = vi.fn()
|
||||
|
@ -34,5 +48,6 @@ HTMLDialogElement.prototype.close = vi.fn(function mock () {
|
|||
})
|
||||
|
||||
window.BASE_URL = 'http://test/'
|
||||
window.createLemonSqueezy = vi.fn()
|
||||
|
||||
Axios.defaults.adapter = vi.fn()
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { screen } from '@testing-library/vue'
|
||||
import { expect, it } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import { plusService } from '@/services'
|
||||
import Form from './ActivateLicenseForm.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
private renderComponent () {
|
||||
return this.render(Form )
|
||||
}
|
||||
|
||||
protected test () {
|
||||
it('activates license', async () => {
|
||||
this.renderComponent()
|
||||
const activateMock = this.mock(plusService, 'activateLicense').mockResolvedValueOnce('')
|
||||
|
||||
await this.type(screen.getByRole('textbox'), 'my-license-key')
|
||||
await this.user.click(screen.getByText('Activate'))
|
||||
expect(activateMock).toHaveBeenCalledWith('my-license-key')
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { screen } from '@testing-library/vue'
|
||||
import { commonStore } from '@/stores'
|
||||
import { expect, it } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import Modal from './KoelPlusModal.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
private renderComponent () {
|
||||
return this.render(Modal)
|
||||
}
|
||||
|
||||
protected test () {
|
||||
it('shows button to purchase Koel Plus', async () => {
|
||||
commonStore.state.koel_plus.product_id = '42'
|
||||
this.renderComponent()
|
||||
|
||||
screen.getByTestId('buttons')
|
||||
expect(screen.queryByTestId('activateForm')).toBeNull()
|
||||
await this.user.click(screen.getByText('Purchase Koel Plus'))
|
||||
expect(global.LemonSqueezy.Url.Open).toHaveBeenCalledWith(
|
||||
'https://store.plus.koel.dev/checkout/buy/42?embed=1&media=0&desc=0'
|
||||
)
|
||||
})
|
||||
|
||||
it('shows form to activate Koel Plus', async () => {
|
||||
commonStore.state.koel_plus.product_id = '42'
|
||||
this.renderComponent()
|
||||
await this.user.click(screen.getByText('I have a license key'))
|
||||
screen.getByTestId('activateForm')
|
||||
})
|
||||
}
|
||||
}
|
|
@ -9,12 +9,12 @@
|
|||
in the future!
|
||||
</div>
|
||||
|
||||
<div class="buttons" v-show="!showingActivateLicenseForm">
|
||||
<div v-show="!showingActivateLicenseForm" class="buttons" data-testid="buttons">
|
||||
<Btn big red @click.prevent="openPurchaseOverlay">Purchase Koel Plus</Btn>
|
||||
<Btn big green @click.prevent="showActivateLicenseForm">I have a license key</Btn>
|
||||
</div>
|
||||
|
||||
<div class="activate-form" v-if="showingActivateLicenseForm">
|
||||
<div v-if="showingActivateLicenseForm" class="activate-form" data-testid="activateForm">
|
||||
<ActivateLicenseForm v-if="showingActivateLicenseForm" />
|
||||
<Btn transparent class="cancel" @click.prevent="hideActivateLicenseForm">Cancel</Btn>
|
||||
</div>
|
||||
|
@ -52,7 +52,7 @@ const openPurchaseOverlay = () => {
|
|||
const showActivateLicenseForm = () => (showingActivateLicenseForm.value = true)
|
||||
const hideActivateLicenseForm = () => (showingActivateLicenseForm.value = false)
|
||||
|
||||
onMounted(() => window.createLemonSqueezy()) // @ts-ignore
|
||||
onMounted(() => window.createLemonSqueezy?.())
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -77,7 +77,7 @@ new class extends UnitTestCase {
|
|||
it('shows new version', () => {
|
||||
commonStore.state.current_version = 'v1.0.0'
|
||||
commonStore.state.latest_version = 'v1.0.1'
|
||||
this.actingAsAdmin().renderComponent()[0].getByRole('button', { name: 'New version available!' })
|
||||
this.beAdmin().renderComponent()[0].getByRole('button', { name: 'New version available!' })
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -19,14 +19,14 @@ const standardItems = [
|
|||
const adminItems = [...standardItems, 'Users', 'Upload', 'Settings']
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test() {
|
||||
protected test () {
|
||||
it('shows the standard items', () => {
|
||||
this.actingAs().render(Sidebar)
|
||||
this.be().render(Sidebar)
|
||||
standardItems.forEach(label => screen.getByText(label))
|
||||
})
|
||||
|
||||
it('shows administrative items', () => {
|
||||
this.actingAsAdmin().render(Sidebar)
|
||||
this.beAdmin().render(Sidebar)
|
||||
adminItems.forEach(label => screen.getByText(label))
|
||||
})
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { eventBus } from '@/utils'
|
|||
import YouTubeSidebarItem from './YouTubeSidebarItem.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test() {
|
||||
protected test () {
|
||||
it('renders', async () => {
|
||||
const { html } = this.render(YouTubeSidebarItem)
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ new class extends UnitTestCase {
|
|||
it('shows new version', () => {
|
||||
commonStore.state.current_version = 'v1.0.0'
|
||||
commonStore.state.latest_version = 'v1.0.1'
|
||||
this.actingAsAdmin().renderComponent().getByTestId('new-version-about')
|
||||
this.beAdmin().renderComponent().getByTestId('new-version-about')
|
||||
})
|
||||
|
||||
it('shows demo notation', async () => {
|
||||
|
|
|
@ -35,6 +35,11 @@ new class extends UnitTestCase {
|
|||
expect((await this.renderComponent()).queryByTestId('support-bar')).toBeNull()
|
||||
})
|
||||
|
||||
it('does not show for Plus edition', async () => {
|
||||
this.enablePlusEdition()
|
||||
expect((await this.renderComponent()).queryByTestId('support-bar')).toBeNull()
|
||||
})
|
||||
|
||||
it('hides', async () => {
|
||||
await this.renderComponent()
|
||||
await this.user.click(screen.getByRole('button', { name: 'Hide' }))
|
||||
|
|
|
@ -36,7 +36,7 @@ const stopBugging = () => {
|
|||
watch(preferenceStore.initialized, initialized => {
|
||||
if (!initialized) return
|
||||
if (preferenceStore.state.supportBarNoBugging || isMobile.any) return
|
||||
if (!isPlus.value) return
|
||||
if (isPlus.value) return
|
||||
|
||||
setUpShowBarTimeout()
|
||||
}, { immediate: true })
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-row rules">
|
||||
<div class="form-row rules" v-koel-overflow-fade>
|
||||
<RuleGroup
|
||||
v-for="(group, index) in collectedRuleGroups"
|
||||
:key="group.id"
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-row rules">
|
||||
<div class="form-row rules" v-koel-overflow-fade>
|
||||
<RuleGroup
|
||||
v-for="(group, index) in mutablePlaylist.rules"
|
||||
:key="group.id"
|
||||
|
|
|
@ -10,6 +10,26 @@
|
|||
<style lang="scss" scoped>
|
||||
.smart-playlist-form {
|
||||
width: 560px;
|
||||
max-height: calc(100vh - 4rem);
|
||||
}
|
||||
|
||||
:slotted(form) {
|
||||
max-height: calc(100vh - 4rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
main {
|
||||
flex-grow: 1;
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.rules {
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:slotted(label.folder) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import ProfileForm from './ProfileForm.vue'
|
|||
|
||||
new class extends UnitTestCase {
|
||||
private async renderComponent (user: User) {
|
||||
return this.actingAs(user).render(ProfileForm)
|
||||
return this.be(user).render(ProfileForm)
|
||||
}
|
||||
|
||||
protected test () {
|
||||
|
|
|
@ -11,9 +11,9 @@ new class extends UnitTestCase {
|
|||
commonStore.state.uses_spotify = useSpotify
|
||||
|
||||
if (isAdmin) {
|
||||
this.actingAsAdmin()
|
||||
this.beAdmin()
|
||||
} else {
|
||||
this.actingAs()
|
||||
this.be()
|
||||
}
|
||||
|
||||
expect(this.render(SpotifyIntegration).html()).toMatchSnapshot();
|
||||
|
|
|
@ -26,13 +26,19 @@ new class extends UnitTestCase {
|
|||
}
|
||||
})
|
||||
|
||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledWith('title', 'asc', 1))
|
||||
return rendered
|
||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledWith({
|
||||
sort: 'title',
|
||||
order: 'asc',
|
||||
page: 1,
|
||||
own_songs_only: false
|
||||
}))
|
||||
|
||||
return [rendered, fetchMock] as const
|
||||
}
|
||||
|
||||
protected test () {
|
||||
it('renders', async () => {
|
||||
const { html } = await this.renderComponent()
|
||||
const [{ html }] = await this.renderComponent()
|
||||
await waitFor(() => expect(html()).toMatchSnapshot())
|
||||
})
|
||||
|
||||
|
@ -42,7 +48,7 @@ new class extends UnitTestCase {
|
|||
const goMock = this.mock(this.router, 'go')
|
||||
await this.renderComponent()
|
||||
|
||||
await this.user.click(screen.getByTitle('Shuffle all songs'))
|
||||
await this.user.click(screen.getByTitle('Shuffle all. Press Alt/⌥ to change mode.'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queueMock).toHaveBeenCalled()
|
||||
|
@ -50,5 +56,20 @@ new class extends UnitTestCase {
|
|||
expect(goMock).toHaveBeenCalledWith('queue')
|
||||
})
|
||||
})
|
||||
|
||||
it('renders in Plus edition', async () => {
|
||||
this.enablePlusEdition()
|
||||
|
||||
const [{ html }, fetchMock] = await this.renderComponent()
|
||||
await waitFor(() => expect(html()).toMatchSnapshot())
|
||||
|
||||
await this.user.click(screen.getByLabelText('Own songs only'))
|
||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledWith({
|
||||
sort: 'title',
|
||||
order: 'asc',
|
||||
page: 1,
|
||||
own_songs_only: true
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,7 +132,12 @@ const fetchSongs = async () => {
|
|||
loading.value = true
|
||||
|
||||
try {
|
||||
page.value = await songStore.paginate(sortField, sortOrder, page.value!, ownSongsOnly.value)
|
||||
page.value = await songStore.paginate({
|
||||
sort: sortField,
|
||||
order: sortOrder,
|
||||
page: page.value!,
|
||||
own_songs_only: ownSongsOnly.value
|
||||
})
|
||||
} catch (error) {
|
||||
toastError('Failed to load songs.')
|
||||
logger.error(error)
|
||||
|
|
|
@ -31,7 +31,11 @@ new class extends UnitTestCase {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(fetchGenreMock).toHaveBeenCalledWith(genre!.name)
|
||||
expect(paginateMock).toHaveBeenCalledWith(genre!.name, 'title', 'asc', 1)
|
||||
expect(paginateMock).toHaveBeenCalledWith(genre!.name, {
|
||||
sort: 'title',
|
||||
order: 'asc',
|
||||
page: 1
|
||||
})
|
||||
})
|
||||
|
||||
await this.tick(2)
|
||||
|
@ -52,7 +56,7 @@ new class extends UnitTestCase {
|
|||
|
||||
await this.renderComponent(genre, songs)
|
||||
|
||||
await this.user.click(screen.getByTitle('Shuffle all songs'))
|
||||
await this.user.click(screen.getByTitle('Shuffle all. Press Alt/⌥ to change mode.'))
|
||||
|
||||
expect(playbackMock).toHaveBeenCalledWith(songs, true)
|
||||
})
|
||||
|
@ -65,7 +69,7 @@ new class extends UnitTestCase {
|
|||
|
||||
await this.renderComponent(genre, songs)
|
||||
|
||||
await this.user.click(screen.getByTitle('Shuffle all songs'))
|
||||
await this.user.click(screen.getByTitle('Shuffle all. Press Alt/⌥ to change mode.'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledWith(genre, 500)
|
||||
|
|
|
@ -103,7 +103,11 @@ const fetch = async () => {
|
|||
|
||||
[genre.value, fetched] = await Promise.all([
|
||||
genreStore.fetchOne(name.value!),
|
||||
songStore.paginateForGenre(name.value!, sortField, sortOrder, page.value!)
|
||||
songStore.paginateForGenre(name.value!, {
|
||||
sort: sortField,
|
||||
order: sortOrder,
|
||||
page: page.value!,
|
||||
})
|
||||
])
|
||||
|
||||
page.value = fetched.nextPage
|
||||
|
|
|
@ -53,7 +53,7 @@ new class extends UnitTestCase {
|
|||
this.renderComponent(songs)
|
||||
const playMock = this.mock(playbackService, 'queueAndPlay')
|
||||
|
||||
await this.user.click(screen.getByTitle('Shuffle all songs'))
|
||||
await this.user.click(screen.getByTitle('Shuffle all. Press Alt/⌥ to change mode.'))
|
||||
await waitFor(() => expect(playMock).toHaveBeenCalledWith(songs, true))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,137 +1,74 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`renders 1`] = `
|
||||
<section id="songsWrapper">
|
||||
<header class="screen-header expanded" data-v-5691beb5="">
|
||||
<aside class="thumbnail-wrapper" data-v-5691beb5="">
|
||||
<div class="thumbnail-stack single" style="background-image: url(undefined/resources/assets/img/covers/default.svg);" data-v-55bfc268="" data-v-5691beb5-s=""><span data-testid="thumbnail" data-v-55bfc268=""></span></div>
|
||||
</aside>
|
||||
<main data-v-5691beb5="">
|
||||
<div class="heading-wrapper" data-v-5691beb5="">
|
||||
<h1 class="name" data-v-5691beb5=""> All Songs
|
||||
<!--v-if-->
|
||||
</h1><span class="meta text-secondary" data-v-5691beb5=""><span data-v-5691beb5-s="">420 songs</span><span data-v-5691beb5-s="">34 hr 17 min</span></span>
|
||||
</div>
|
||||
<div class="song-list-controls" data-testid="song-list-controls" data-v-d396e0d2="" data-v-5691beb5-s="">
|
||||
<div class="wrapper" data-v-d396e0d2=""><span class="btn-group" uppercased="" data-v-e884c19a="" data-v-d396e0d2=""><button type="button" class="btn-shuffle-all" data-testid="btn-shuffle-all" orange="" title="Shuffle all songs" data-v-e368fe26="" data-v-d396e0d2=""><br data-testid="icon" icon="[object Object]" fixed-width="" data-v-d396e0d2=""> All </button><!--v-if--><!--v-if--><!--v-if--></span>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div class="menu-wrapper" data-v-d396e0d2="">
|
||||
<div class="add-to" data-testid="add-to-menu" tabindex="0" data-v-42061e3e="" data-v-d396e0d2="">
|
||||
<section class="existing-playlists" data-v-42061e3e="">
|
||||
<p data-v-42061e3e="">Add 0 songs to</p>
|
||||
<ul data-v-42061e3e="">
|
||||
<li data-testid="queue" tabindex="0" data-v-42061e3e="">Queue</li>
|
||||
<li class="favorites" data-testid="add-to-favorites" tabindex="0" data-v-42061e3e=""> Favorites </li>
|
||||
</ul>
|
||||
</section><button type="button" transparent="" data-v-e368fe26="" data-v-42061e3e="">New Playlist…</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</header><br data-testid="song-list">
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`renders 2`] = `
|
||||
<section id="songsWrapper">
|
||||
<header class="screen-header expanded" data-v-5691beb5="">
|
||||
<aside class="thumbnail-wrapper" data-v-5691beb5="">
|
||||
<div class="thumbnail-stack single" style="background-image: url(undefined/resources/assets/img/covers/default.svg);" data-v-55bfc268="" data-v-5691beb5-s=""><span data-testid="thumbnail" data-v-55bfc268=""></span></div>
|
||||
</aside>
|
||||
<main data-v-5691beb5="">
|
||||
<div class="heading-wrapper" data-v-5691beb5="">
|
||||
<h1 class="name" data-v-5691beb5=""> All Songs
|
||||
<!--v-if-->
|
||||
</h1><span class="meta text-secondary" data-v-5691beb5=""><span data-v-5691beb5-s="">420 songs</span><span data-v-5691beb5-s="">34 hr 17 min</span></span>
|
||||
</div>
|
||||
<div class="song-list-controls" data-testid="song-list-controls" data-v-d396e0d2="" data-v-5691beb5-s="">
|
||||
<div class="wrapper" data-v-d396e0d2=""><span class="btn-group" uppercased="" data-v-e884c19a="" data-v-d396e0d2=""><button class="btn-shuffle-all" data-testid="btn-shuffle-all" orange="" title="Shuffle all songs" data-v-e368fe26="" data-v-d396e0d2=""><br data-testid="icon" icon="[object Object]" fixed-width="" data-v-d396e0d2=""> All </button><!--v-if--><!--v-if--><!--v-if--></span>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div class="menu-wrapper" data-v-d396e0d2="">
|
||||
<div class="add-to" data-testid="add-to-menu" tabindex="0" data-v-42061e3e="" data-v-d396e0d2="">
|
||||
<section class="existing-playlists" data-v-42061e3e="">
|
||||
<p data-v-42061e3e="">Add 0 songs to</p>
|
||||
<ul data-v-42061e3e="">
|
||||
<li data-testid="queue" tabindex="0" data-v-42061e3e="">Queue</li>
|
||||
<li class="favorites" data-testid="add-to-favorites" tabindex="0" data-v-42061e3e=""> Favorites </li>
|
||||
</ul>
|
||||
</section><button transparent="" data-v-e368fe26="" data-v-42061e3e="">New Playlist…</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</header><br data-testid="song-list">
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`renders 3`] = `
|
||||
<section id="songsWrapper">
|
||||
<header data-v-5691beb5="" class="screen-header expanded">
|
||||
<section data-v-8ea4eaa5="" id="songsWrapper">
|
||||
<header data-v-5691beb5="" data-v-8ea4eaa5="" class="screen-header expanded">
|
||||
<aside data-v-5691beb5="" class="thumbnail-wrapper">
|
||||
<div data-v-55bfc268="" data-v-5691beb5-s="" class="thumbnail-stack single" style="background-image: url(undefined/resources/assets/img/covers/default.svg);"><span data-v-55bfc268="" data-testid="thumbnail"></span></div>
|
||||
<div data-v-55bfc268="" data-v-8ea4eaa5="" data-v-5691beb5-s="" class="thumbnail-stack single" style="background-image: url(undefined/resources/assets/img/covers/default.svg);"><span data-v-55bfc268="" data-testid="thumbnail"></span></div>
|
||||
</aside>
|
||||
<main data-v-5691beb5="">
|
||||
<div data-v-5691beb5="" class="heading-wrapper">
|
||||
<h1 data-v-5691beb5="" class="name"> All Songs
|
||||
<!--v-if-->
|
||||
</h1><span data-v-5691beb5="" class="meta text-secondary"><span data-v-5691beb5-s="">420 songs</span><span data-v-5691beb5-s="">34 hr 17 min</span></span>
|
||||
</h1><span data-v-5691beb5="" class="meta text-secondary"><span data-v-8ea4eaa5="" data-v-5691beb5-s="">420 songs</span><span data-v-8ea4eaa5="" data-v-5691beb5-s="">34 hr 17 min</span></span>
|
||||
</div>
|
||||
<div data-v-d396e0d2="" data-v-5691beb5-s="" class="song-list-controls" data-testid="song-list-controls">
|
||||
<div data-v-d396e0d2="" class="wrapper"><span data-v-e884c19a="" data-v-d396e0d2="" class="btn-group" uppercased=""><button data-v-e368fe26="" data-v-d396e0d2="" class="btn-shuffle-all" data-testid="btn-shuffle-all" orange="" title="Shuffle all songs"><br data-v-d396e0d2="" data-testid="icon" icon="[object Object]" fixed-width=""> All </button><!--v-if--><!--v-if--><!--v-if--></span>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div data-v-d396e0d2="" class="menu-wrapper">
|
||||
<div data-v-42061e3e="" data-v-d396e0d2="" class="add-to" data-testid="add-to-menu" tabindex="0">
|
||||
<section data-v-42061e3e="" class="existing-playlists">
|
||||
<p data-v-42061e3e="">Add 0 songs to</p>
|
||||
<ul data-v-42061e3e="">
|
||||
<li data-v-42061e3e="" data-testid="queue" tabindex="0">Queue</li>
|
||||
<li data-v-42061e3e="" class="favorites" data-testid="add-to-favorites" tabindex="0"> Favorites </li>
|
||||
</ul>
|
||||
</section><button data-v-e368fe26="" data-v-42061e3e="" transparent="">New Playlist…</button>
|
||||
<div data-v-8ea4eaa5="" data-v-5691beb5-s="" class="controls">
|
||||
<div data-v-d396e0d2="" data-v-8ea4eaa5="" data-v-5691beb5-s="" class="song-list-controls" data-testid="song-list-controls">
|
||||
<div data-v-d396e0d2="" class="wrapper"><span data-v-e884c19a="" data-v-d396e0d2="" class="btn-group" uppercased=""><button data-v-e368fe26="" data-v-d396e0d2="" class="btn-shuffle-all" data-testid="btn-shuffle-all" orange="" title="Shuffle all. Press Alt/⌥ to change mode."><br data-v-d396e0d2="" data-testid="Icon" icon="[object Object]" fixed-width=""> All </button><!--v-if--><!--v-if--><!--v-if--></span>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div data-v-d396e0d2="" class="menu-wrapper">
|
||||
<div data-v-42061e3e="" data-v-d396e0d2="" class="add-to" data-testid="add-to-menu" tabindex="0">
|
||||
<section data-v-42061e3e="" class="existing-playlists">
|
||||
<p data-v-42061e3e="">Add 0 songs to</p>
|
||||
<ul data-v-42061e3e="">
|
||||
<li data-v-42061e3e="" data-testid="queue" tabindex="0">Queue</li>
|
||||
<li data-v-42061e3e="" class="favorites" data-testid="add-to-favorites" tabindex="0"> Favorites </li>
|
||||
</ul>
|
||||
</section><button data-v-e368fe26="" data-v-42061e3e="" transparent="">New Playlist…</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
</main>
|
||||
</header><br data-testid="song-list">
|
||||
</header><br data-v-8ea4eaa5="" data-testid="song-list">
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`renders 4`] = `
|
||||
<section id="songsWrapper">
|
||||
<header data-v-5691beb5="" class="screen-header expanded">
|
||||
exports[`renders in Plus edition 1`] = `
|
||||
<section data-v-8ea4eaa5="" id="songsWrapper">
|
||||
<header data-v-5691beb5="" data-v-8ea4eaa5="" class="screen-header expanded">
|
||||
<aside data-v-5691beb5="" class="thumbnail-wrapper">
|
||||
<div data-v-55bfc268="" data-v-5691beb5-s="" class="thumbnail-stack single" style="background-image: url(undefined/resources/assets/img/covers/default.svg);"><span data-v-55bfc268="" data-testid="thumbnail"></span></div>
|
||||
<div data-v-55bfc268="" data-v-8ea4eaa5="" data-v-5691beb5-s="" class="thumbnail-stack single" style="background-image: url(undefined/resources/assets/img/covers/default.svg);"><span data-v-55bfc268="" data-testid="thumbnail"></span></div>
|
||||
</aside>
|
||||
<main data-v-5691beb5="">
|
||||
<div data-v-5691beb5="" class="heading-wrapper">
|
||||
<h1 data-v-5691beb5="" class="name"> All Songs
|
||||
<!--v-if-->
|
||||
</h1><span data-v-5691beb5="" class="meta text-secondary"><span data-v-5691beb5-s="">420 songs</span><span data-v-5691beb5-s="">34 hr 17 min</span></span>
|
||||
</h1><span data-v-5691beb5="" class="meta text-secondary"><span data-v-8ea4eaa5="" data-v-5691beb5-s="">420 songs</span><span data-v-8ea4eaa5="" data-v-5691beb5-s="">34 hr 17 min</span></span>
|
||||
</div>
|
||||
<div data-v-d396e0d2="" data-v-5691beb5-s="" class="song-list-controls" data-testid="song-list-controls">
|
||||
<div data-v-d396e0d2="" class="wrapper"><span data-v-e884c19a="" data-v-d396e0d2="" class="btn-group" uppercased=""><button data-v-e368fe26="" data-v-d396e0d2="" class="btn-shuffle-all" data-testid="btn-shuffle-all" orange="" title="Shuffle all songs"><br data-v-d396e0d2="" data-testid="Icon" icon="[object Object]" fixed-width=""> All </button><!--v-if--><!--v-if--><!--v-if--></span>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div data-v-d396e0d2="" class="menu-wrapper">
|
||||
<div data-v-42061e3e="" data-v-d396e0d2="" class="add-to" data-testid="add-to-menu" tabindex="0">
|
||||
<section data-v-42061e3e="" class="existing-playlists">
|
||||
<p data-v-42061e3e="">Add 0 songs to</p>
|
||||
<ul data-v-42061e3e="">
|
||||
<li data-v-42061e3e="" data-testid="queue" tabindex="0">Queue</li>
|
||||
<li data-v-42061e3e="" class="favorites" data-testid="add-to-favorites" tabindex="0"> Favorites </li>
|
||||
</ul>
|
||||
</section><button data-v-e368fe26="" data-v-42061e3e="" transparent="">New Playlist…</button>
|
||||
<div data-v-8ea4eaa5="" data-v-5691beb5-s="" class="controls">
|
||||
<div data-v-d396e0d2="" data-v-8ea4eaa5="" data-v-5691beb5-s="" class="song-list-controls" data-testid="song-list-controls">
|
||||
<div data-v-d396e0d2="" class="wrapper"><span data-v-e884c19a="" data-v-d396e0d2="" class="btn-group" uppercased=""><button data-v-e368fe26="" data-v-d396e0d2="" class="btn-shuffle-all" data-testid="btn-shuffle-all" orange="" title="Shuffle all. Press Alt/⌥ to change mode."><br data-v-d396e0d2="" data-testid="Icon" icon="[object Object]" fixed-width=""> All </button><!--v-if--><!--v-if--><!--v-if--></span>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-d396e0d2="" class="menu-wrapper">
|
||||
<div data-v-42061e3e="" data-v-d396e0d2="" class="add-to" data-testid="add-to-menu" tabindex="0">
|
||||
<section data-v-42061e3e="" class="existing-playlists">
|
||||
<p data-v-42061e3e="">Add 0 songs to</p>
|
||||
<ul data-v-42061e3e="">
|
||||
<li data-v-42061e3e="" data-testid="queue" tabindex="0">Queue</li>
|
||||
<li data-v-42061e3e="" class="favorites" data-testid="add-to-favorites" tabindex="0"> Favorites </li>
|
||||
</ul>
|
||||
</section><button data-v-e368fe26="" data-v-42061e3e="" transparent="">New Playlist…</button>
|
||||
</div>
|
||||
</div>
|
||||
</div><label data-v-8ea4eaa5="" data-v-5691beb5-s="" class="own-songs-toggle text-secondary"><span data-v-b5259680="" data-v-8ea4eaa5="" data-v-5691beb5-s="" class=""><input data-v-b5259680="" type="checkbox"></span><span data-v-8ea4eaa5="" data-v-5691beb5-s="">Own songs only</span></label>
|
||||
</div>
|
||||
</main>
|
||||
</header><br data-testid="song-list">
|
||||
</header><br data-v-8ea4eaa5="" data-testid="song-list">
|
||||
</section>
|
||||
`;
|
||||
|
|
|
@ -247,7 +247,7 @@ new class extends UnitTestCase {
|
|||
})
|
||||
|
||||
it('allows edit songs if current user is admin', async () => {
|
||||
await this.actingAsAdmin().renderComponent()
|
||||
await this.beAdmin().renderComponent()
|
||||
|
||||
// mock after render to ensure that the component is mounted properly
|
||||
const emitMock = this.mock(eventBus, 'emit')
|
||||
|
@ -257,7 +257,7 @@ new class extends UnitTestCase {
|
|||
})
|
||||
|
||||
it('does not allow edit songs if current user is not admin', async () => {
|
||||
await this.actingAs().renderComponent()
|
||||
await this.be().renderComponent()
|
||||
expect(screen.queryByText('Edit…')).toBeNull()
|
||||
})
|
||||
|
||||
|
@ -270,7 +270,7 @@ new class extends UnitTestCase {
|
|||
const confirmMock = this.mock(DialogBoxStub.value, 'confirm', true)
|
||||
const toasterMock = this.mock(MessageToasterStub.value, 'success')
|
||||
const deleteMock = this.mock(songStore, 'deleteFromFilesystem')
|
||||
await this.actingAsAdmin().renderComponent()
|
||||
await this.beAdmin().renderComponent()
|
||||
|
||||
const emitMock = this.mock(eventBus, 'emit')
|
||||
|
||||
|
@ -285,12 +285,12 @@ new class extends UnitTestCase {
|
|||
})
|
||||
|
||||
it('does not have an option to delete songs if current user is not admin', async () => {
|
||||
await this.actingAs().renderComponent()
|
||||
await this.be().renderComponent()
|
||||
expect(screen.queryByText('Delete from Filesystem')).toBeNull()
|
||||
})
|
||||
|
||||
it('creates playlist from selected songs', async () => {
|
||||
await this.actingAs().renderComponent()
|
||||
await this.be().renderComponent()
|
||||
|
||||
// mock after render to ensure that the component is mounted properly
|
||||
const emitMock = this.mock(eventBus, 'emit')
|
||||
|
|
|
@ -4,7 +4,7 @@ import UnitTestCase from '@/__tests__/UnitTestCase'
|
|||
import SongListFilter from './SongListFilter.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test() {
|
||||
protected test () {
|
||||
it('emit an event on input', async () => {
|
||||
const { emitted } = this.render(SongListFilter)
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`renders 1`] = `<div class="playing song-item" data-testid="song-item" tabindex="0"><span class="track-number"><i data-v-47e95701=""><span data-v-47e95701=""></span><span data-v-47e95701=""></span><span data-v-47e95701=""></span></i></span><span class="thumbnail"><div data-v-a2b2e00f="" style="background-image: url(undefined/resources/assets/img/covers/default.svg);" class="cover"><img data-v-a2b2e00f="" alt="Test Album" src="https://example.com/cover.jpg" loading="lazy"><a data-v-a2b2e00f="" title="Pause" class="control" role="button"><br data-v-a2b2e00f="" data-testid="Icon" icon="[object Object]" class="text-highlight"></a></div></span><span class="title-artist"><span class="title text-primary">Test Song</span><span class="artist">Test Artist</span></span><span class="album">Test Album</span><span class="time">16:40</span><span class="extra"><button title="Unlike Test Song by Test Artist" type="button"><br data-testid="Icon" icon="[object Object]"></button></span></div>`;
|
||||
exports[`renders 1`] = `<div class="playing song-item" data-testid="song-item" tabindex="0"><span class="track-number"><i data-v-47e95701=""><span data-v-47e95701=""></span><span data-v-47e95701=""></span><span data-v-47e95701=""></span></i></span><span class="thumbnail"><div data-v-a2b2e00f="" style="background-image: url(undefined/resources/assets/img/covers/default.svg);" class="cover"><img data-v-a2b2e00f="" alt="Test Album" src="https://example.com/cover.jpg" loading="lazy"><a data-v-a2b2e00f="" title="Pause" class="control" role="button"><br data-v-a2b2e00f="" data-testid="Icon" icon="[object Object]" class="text-highlight"></a></div></span><span class="title-artist"><span class="title text-primary"><!--v-if--> Test Song</span><span class="artist">Test Artist</span></span><span class="album">Test Album</span><span class="time">16:40</span><span class="extra"><button title="Unlike Test Song by Test Artist" type="button"><br data-testid="Icon" icon="[object Object]"></button></span></div>`;
|
||||
|
|
|
@ -31,7 +31,7 @@ new class extends UnitTestCase {
|
|||
const song = factory<Song>('song', { lyrics: null })
|
||||
|
||||
const mock = this.mock(eventBus, 'emit')
|
||||
this.actingAsAdmin().renderComponent(song)
|
||||
this.beAdmin().renderComponent(song)
|
||||
|
||||
await this.user.click(screen.getByRole('button', { name: 'Click here' }))
|
||||
|
||||
|
@ -39,7 +39,7 @@ new class extends UnitTestCase {
|
|||
})
|
||||
|
||||
it('does not have a button to add lyrics if current user is not an admin', async () => {
|
||||
this.actingAs().renderComponent(factory<Song>('song', { lyrics: null }))
|
||||
this.be().renderComponent(factory<Song>('song', { lyrics: null }))
|
||||
expect(screen.queryByRole('button', { name: 'Click here' })).toBeNull()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ new class extends UnitTestCase {
|
|||
avatar: 'https://example.com/avatar.jpg'
|
||||
})
|
||||
|
||||
expect(this.actingAs(user).render(ProfileAvatar).html()).toMatchSnapshot()
|
||||
expect(this.be(user).render(ProfileAvatar).html()).toMatchSnapshot()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import UserBadge from './UserBadge.vue'
|
|||
|
||||
new class extends UnitTestCase {
|
||||
private renderComponent () {
|
||||
return this.actingAs(factory<User>('user', {
|
||||
return this.be(factory<User>('user', {
|
||||
name: 'John Doe'
|
||||
})).render(UserBadge)
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ new class extends UnitTestCase {
|
|||
protected test () {
|
||||
it('has different behaviors for current user', () => {
|
||||
const user = factory<User>('user')
|
||||
this.actingAs(user).renderComponent(user)
|
||||
this.be(user).renderComponent(user)
|
||||
|
||||
screen.getByTitle('This is you!')
|
||||
screen.getByText('Your Profile')
|
||||
|
@ -39,7 +39,7 @@ new class extends UnitTestCase {
|
|||
it('redirects to Profile screen if edit current user', async () => {
|
||||
const mock = this.mock(this.router, 'go')
|
||||
const user = factory<User>('user')
|
||||
this.actingAs(user).renderComponent(user)
|
||||
this.be(user).renderComponent(user)
|
||||
|
||||
await this.user.click(screen.getByRole('button', { name: 'Your Profile' }))
|
||||
|
||||
|
@ -49,7 +49,7 @@ new class extends UnitTestCase {
|
|||
it('deletes user if confirmed', async () => {
|
||||
this.mock(DialogBoxStub.value, 'confirm').mockResolvedValue(true)
|
||||
const user = factory<User>('user')
|
||||
this.actingAsAdmin().renderComponent(user)
|
||||
this.beAdmin().renderComponent(user)
|
||||
const destroyMock = this.mock(userStore, 'destroy')
|
||||
|
||||
await this.user.click(screen.getByRole('button', { name: 'Delete' }))
|
||||
|
@ -60,7 +60,7 @@ new class extends UnitTestCase {
|
|||
it('does not delete user if not confirmed', async () => {
|
||||
this.mock(DialogBoxStub.value, 'confirm').mockResolvedValue(false)
|
||||
const user = factory<User>('user')
|
||||
this.actingAsAdmin().renderComponent(user)
|
||||
this.beAdmin().renderComponent(user)
|
||||
const destroyMock = this.mock(userStore, 'destroy')
|
||||
|
||||
await this.user.click(screen.getByRole('button', { name: 'Delete' }))
|
||||
|
@ -71,7 +71,7 @@ new class extends UnitTestCase {
|
|||
it('revokes invite for prospects', async () => {
|
||||
this.mock(DialogBoxStub.value, 'confirm').mockResolvedValue(true)
|
||||
const prospect = factory.states('prospect')<User>('user')
|
||||
this.actingAsAdmin().renderComponent(prospect)
|
||||
this.beAdmin().renderComponent(prospect)
|
||||
const revokeMock = this.mock(invitationService, 'revoke')
|
||||
|
||||
await this.user.click(screen.getByRole('button', { name: 'Revoke' }))
|
||||
|
@ -82,7 +82,7 @@ new class extends UnitTestCase {
|
|||
it('does not revoke invite for prospects if not confirmed', async () => {
|
||||
this.mock(DialogBoxStub.value, 'confirm').mockResolvedValue(false)
|
||||
const prospect = factory.states('prospect')<User>('user')
|
||||
this.actingAsAdmin().renderComponent(prospect)
|
||||
this.beAdmin().renderComponent(prospect)
|
||||
const revokeMock = this.mock(invitationService, 'revoke')
|
||||
|
||||
await this.user.click(screen.getByRole('button', { name: 'Revoke' }))
|
||||
|
|
|
@ -88,13 +88,8 @@ export default class Router {
|
|||
return this.cache.get(location.hash)
|
||||
}
|
||||
|
||||
public async triggerNotFound () {
|
||||
await this.activateRoute(this.notFoundRoute)
|
||||
}
|
||||
|
||||
public onRouteChanged (handler: RouteChangedHandler) {
|
||||
this.routeChangedHandlers.push(handler)
|
||||
}
|
||||
public triggerNotFound = async () => await this.activateRoute(this.notFoundRoute)
|
||||
public onRouteChanged = (handler: RouteChangedHandler) => this.routeChangedHandlers.push(handler)
|
||||
|
||||
public async activateRoute (route: Route, params: RouteParams = {}) {
|
||||
this.$currentRoute.value = route
|
||||
|
|
|
@ -130,18 +130,6 @@ class PlaybackService {
|
|||
})
|
||||
}
|
||||
|
||||
public setCurrentSongAndPlaybackPosition (song: Song, playbackPosition: number) {
|
||||
if (queueStore.current) {
|
||||
queueStore.current.playback_state = 'Stopped'
|
||||
}
|
||||
|
||||
song.playback_state = 'Paused'
|
||||
|
||||
this.player.media.src = songStore.getSourceUrl(song)
|
||||
this.player.seek(playbackPosition)
|
||||
this.player.pause()
|
||||
}
|
||||
|
||||
// Record the UNIX timestamp the song starts playing, for scrobbling purpose
|
||||
private recordStartTime (song: Song) {
|
||||
song.play_start_time = Math.floor(Date.now() / 1000)
|
||||
|
|
|
@ -274,9 +274,14 @@ new class extends UnitTestCase {
|
|||
|
||||
const syncMock = this.mock(songStore, 'syncWithVault', reactive(songs))
|
||||
|
||||
expect(await songStore.paginate('title', 'desc', 2)).toBe(3)
|
||||
expect(await songStore.paginate({
|
||||
page: 2,
|
||||
sort: 'title',
|
||||
order: 'desc',
|
||||
own_songs_only: true
|
||||
})).toBe(3)
|
||||
|
||||
expect(getMock).toHaveBeenCalledWith('songs?page=2&sort=title&order=desc')
|
||||
expect(getMock).toHaveBeenCalledWith('songs?page=2&sort=title&order=desc&own_songs_only=true')
|
||||
expect(syncMock).toHaveBeenCalledWith(songs)
|
||||
expect(songStore.state.songs).toEqual(reactive(songs))
|
||||
})
|
||||
|
@ -297,7 +302,11 @@ new class extends UnitTestCase {
|
|||
|
||||
const syncMock = this.mock(songStore, 'syncWithVault', reactiveSongs)
|
||||
|
||||
expect(await songStore.paginateForGenre('foo', 'title', 'desc', 2)).toEqual({
|
||||
expect(await songStore.paginateForGenre('foo', {
|
||||
page: 2,
|
||||
sort: 'title',
|
||||
order: 'desc'
|
||||
})).toEqual({
|
||||
songs: reactiveSongs,
|
||||
nextPage: 3
|
||||
})
|
||||
|
|
|
@ -29,6 +29,19 @@ export interface SongUpdateResult {
|
|||
}
|
||||
}
|
||||
|
||||
export interface SongListPaginateParams extends Record<string, any> {
|
||||
sort: SongListSortField
|
||||
order: SortOrder
|
||||
page: number
|
||||
own_songs_only: boolean
|
||||
}
|
||||
|
||||
export interface GenreSongListPaginateParams extends Record<string, any> {
|
||||
sort: SongListSortField
|
||||
order: SortOrder
|
||||
page: number
|
||||
}
|
||||
|
||||
export const songStore = {
|
||||
vault: new Map<string, UnwrapNestedRefs<Song>>(),
|
||||
|
||||
|
@ -184,13 +197,9 @@ export const songStore = {
|
|||
return uniqBy(songs, 'id')
|
||||
},
|
||||
|
||||
async paginateForGenre (genre: Genre | string, sortField: SongListSortField, sortOrder: SortOrder, page: number) {
|
||||
async paginateForGenre (genre: Genre | string, params: GenreSongListPaginateParams) {
|
||||
const name = typeof genre === 'string' ? genre : genre.name
|
||||
|
||||
const resource = await http.get<PaginatorResource>(
|
||||
`genres/${name}/songs?page=${page}&sort=${sortField}&order=${sortOrder}`
|
||||
)
|
||||
|
||||
const resource = await http.get<PaginatorResource>(`genres/${name}/songs?${new URLSearchParams(params).toString()}`)
|
||||
const songs = this.syncWithVault(resource.data)
|
||||
|
||||
return {
|
||||
|
@ -204,11 +213,8 @@ export const songStore = {
|
|||
return this.syncWithVault(await http.get<Song[]>(`genres/${name}/songs/random?limit=${limit}`))
|
||||
},
|
||||
|
||||
async paginate (sortField: SongListSortField, sortOrder: SortOrder, page: number, ownSongOnly: boolean) {
|
||||
const resource = await http.get<PaginatorResource>(
|
||||
`songs?page=${page}&sort=${sortField}&order=${sortOrder}&ownSongsOnly=${ownSongOnly ? 1 : 0}`
|
||||
)
|
||||
|
||||
async paginate (params: SongListPaginateParams) {
|
||||
const resource = await http.get<PaginatorResource>(`songs?${new URLSearchParams(params).toString()}`)
|
||||
this.state.songs = unionBy(this.state.songs, this.syncWithVault(resource.data), 'id')
|
||||
|
||||
return resource.links.next ? ++resource.meta.current_page : null
|
||||
|
|
1
resources/assets/js/types.d.ts
vendored
1
resources/assets/js/types.d.ts
vendored
|
@ -60,6 +60,7 @@ interface Window {
|
|||
readonly PUSHER_APP_KEY: string
|
||||
readonly PUSHER_APP_CLUSTER: string
|
||||
readonly MediaMetadata: Constructable<Record<string, any>>
|
||||
createLemonSqueezy?: () => Closure
|
||||
}
|
||||
|
||||
interface FileSystemDirectoryReader {
|
||||
|
|
|
@ -18,6 +18,37 @@ class SongTest extends TestCase
|
|||
License::fakePlusLicense();
|
||||
}
|
||||
|
||||
public function testWithOwnSongsOnlyOptionOn(): void
|
||||
{
|
||||
$user = create_user();
|
||||
|
||||
Song::factory(2)->public()->create();
|
||||
$ownSongs = Song::factory(3)->for($user, 'owner')->create();
|
||||
|
||||
$this->getAs('api/songs?own_songs_only=true', $user)
|
||||
->assertSuccessful()
|
||||
->assertJsonCount(3, 'data')
|
||||
->assertJsonFragment(['id' => $ownSongs[0]->id])
|
||||
->assertJsonFragment(['id' => $ownSongs[1]->id])
|
||||
->assertJsonFragment(['id' => $ownSongs[2]->id]);
|
||||
}
|
||||
|
||||
public function testWithOwnSongsOnlyOptionOffOrMissing(): void
|
||||
{
|
||||
$user = create_user();
|
||||
|
||||
Song::factory(2)->public()->create();
|
||||
Song::factory(3)->for($user, 'owner')->create();
|
||||
|
||||
$this->getAs('api/songs?own_songs_only=false', $user)
|
||||
->assertSuccessful()
|
||||
->assertJsonCount(5, 'data');
|
||||
|
||||
$this->getAs('api/songs', $user)
|
||||
->assertSuccessful()
|
||||
->assertJsonCount(5, 'data');
|
||||
}
|
||||
|
||||
public function testShowSongPolicy(): void
|
||||
{
|
||||
$user = create_user();
|
||||
|
|
Loading…
Reference in a new issue