diff --git a/package.json b/package.json index f49128d0..e69254ff 100644 --- a/package.json +++ b/package.json @@ -66,9 +66,9 @@ "factoria": "^4.0.0", "file-loader": "^1.1.6", "font-awesome": "^4.7.0", - "happy-dom": "^3.1.0", "husky": "^4.2.5", "jest-serializer-vue": "^2.0.2", + "jsdom": "^19.0.0", "kill-port": "^1.6.1", "laravel-mix": "^6.0.43", "lint-staged": "^10.3.0", diff --git a/resources/assets/js/__tests__/UnitTestCase.ts b/resources/assets/js/__tests__/UnitTestCase.ts index f60b2bcf..c2519d78 100644 --- a/resources/assets/js/__tests__/UnitTestCase.ts +++ b/resources/assets/js/__tests__/UnitTestCase.ts @@ -7,8 +7,6 @@ import { defineComponent, nextTick } from 'vue' import { commonStore, userStore } from '@/stores' import factory from '@/__tests__/factory' -declare type Methods = { [K in keyof T]: T[K] extends Closure ? K : never; }[keyof T] & (string | symbol); - export default abstract class UnitTestCase { private backupMethods = new Map() @@ -44,7 +42,7 @@ export default abstract class UnitTestCase { return this.actingAs(factory.states('admin')('user')) } - protected mock>> (obj: T, methodName: M, implementation?: any) { + protected mock>> (obj: T, methodName: M, implementation?: any) { const mock = vi.fn() if (implementation !== undefined) { @@ -88,5 +86,11 @@ export default abstract class UnitTestCase { } } + protected setReadOnlyProperty (obj: T, prop: keyof T, value: any) { + return Object.defineProperty(obj, prop, { + get: () => value + }) + } + protected abstract test () } diff --git a/resources/assets/js/__tests__/__mocks__/lodash.ts b/resources/assets/js/__tests__/__mocks__/lodash.ts deleted file mode 100644 index a58a7ce0..00000000 --- a/resources/assets/js/__tests__/__mocks__/lodash.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint @typescript-eslint/no-unused-vars: 0 */ -import _ from 'lodash' - -_.orderBy = jest.fn( (collection: T[]): T[] => collection) - -_.shuffle = jest.fn( (collection: T[]): T[] => collection) - -_.throttle = jest.fn((fn: Function, wait: number): any => fn) - -_.sample = jest.fn( (collection: T[]): T | undefined => { - return collection.length ? collection[0] : undefined -}) - -module.exports = _ diff --git a/resources/assets/js/__tests__/services/playback.spec.ts b/resources/assets/js/__tests__/services/playback.spec.ts deleted file mode 100644 index 84ac6ccc..00000000 --- a/resources/assets/js/__tests__/services/playback.spec.ts +++ /dev/null @@ -1,496 +0,0 @@ -import plyr from 'plyr' -import { orderBy, shuffle } from 'lodash' -import { playbackService, socketService } from '@/services' -import { eventBus, noop } from '@/utils' -import { mock } from '@/__tests__/__helpers__' -import { - commonStore, - preferenceStore as preferences, - queueStore, - recentlyPlayedStore, - songStore, - userStore -} from '@/stores' -import router from '@/router' -import factory from '@/__tests__/factory' -import Vue from 'vue' -import FunctionPropertyNames = jest.FunctionPropertyNames - -const prepareForTests = () => { - document.body.innerHTML = ` -
- -
- - ` - window.AudioContext = jest.fn().mockImplementation(() => { - return { - createMediaElementSource: jest.fn(noop) - } - }) -} - -describe('services/playback', () => { - beforeEach(() => prepareForTests()) - - afterEach(() => { - jest.resetModules() - jest.restoreAllMocks() - jest.clearAllMocks() - }) - - it('only initializes once', () => { - const plyrSetupSpy = jest.spyOn(plyr, 'setup') - playbackService.init() - expect(plyrSetupSpy).toHaveBeenCalled() - playbackService.init() - expect(plyrSetupSpy).toHaveBeenCalledTimes(1) - }) - - describe('listens to media events', () => { - it.each<[boolean, boolean, number, number, number]>([ - /* playCountRegistered, isTranscoding, current media time, media duration, number of registerPlay()'s calls */ - [false, false, 100, 400, 1], - [true, false, 100, 400, 0], - [false, true, 100, 400, 0], - [false, false, 100, 500, 0] - ])( - 'when playCountRegistered is %s, isTranscoding is %s, current media time is %d, media duration is %d, then registerPlay() should be call %d times', - (playCountRegistered, isTranscoding, currentTime, duration, numberOfCalls) => { - queueStore.current = factory('song', { playCountRegistered }) - Object.defineProperty(playbackService, 'isTranscoding', { get: () => isTranscoding }) - playbackService.init() - const mediaElement = playbackService.player!.media - // we can't set mediaElement.currentTime|duration directly because they're read-only - Object.defineProperties(mediaElement, { - currentTime: { - value: currentTime, - configurable: true - }, - duration: { - value: duration, - configurable: true - } - }) - - const registerPlayMock = mock(playbackService, 'registerPlay') - mediaElement.dispatchEvent(new Event('timeupdate')) - expect(registerPlayMock).toHaveBeenCalledTimes(numberOfCalls) - }) - - it('plays next song if current song is errored', () => { - playbackService.init() - const playNextMock = mock(playbackService, 'playNext') - playbackService.player!.media.dispatchEvent(new Event('error')) - expect(playNextMock).toHaveBeenCalled() - }) - - it('scrobbles if current song ends', () => { - queueStore.current = factory('song') - commonStore.state.useLastfm = true - userStore.current = factory('user', { - preferences: { - lastfm_session_key: 'foo' - } - }) - - playbackService.init() - const scrobbleMock = mock(songStore, 'scrobble') - playbackService.player!.media.dispatchEvent(new Event('ended')) - expect(scrobbleMock).toHaveBeenCalled() - }) - - it.each<[RepeatMode, number, number]>([['REPEAT_ONE', 1, 0], ['NO_REPEAT', 0, 1], ['REPEAT_ALL', 0, 1]])( - 'when song ends, if repeat mode is %s then restart() is called %d times and playNext() is called %d times', - (repeatMode, restartCalls, playNextCalls) => { - commonStore.state.useLastfm = false // so that no scrobbling is made unnecessarily - preferences.repeatMode = repeatMode - playbackService.init() - const restartMock = mock(playbackService, 'restart') - const playNextMock = mock(playbackService, 'playNext') - playbackService.player!.media.dispatchEvent(new Event('ended')) - expect(restartMock).toHaveBeenCalledTimes(restartCalls) - expect(playNextMock).toHaveBeenCalledTimes(playNextCalls) - }) - - it.each([ - [false, true, 300, 310, 0], - [true, false, 300, 310, 0], - [false, false, 300, 400, 0], - [false, false, 300, 310, 1] - ])( - 'when next song preloaded is %s, isTrancoding is %s, current media time is %d, media duration is %d, then preload() should be called %d times', - (preloaded, isTranscoding, currentTime, duration, numberOfCalls) => { - queueStore.current = factory('song', { playCountRegistered: true }) // avoid triggering play count logic - Object.defineProperty(queueStore, 'next', { - get: () => factory('song', { preloaded }) - }) - Object.defineProperty(playbackService, 'isTranscoding', { get: () => isTranscoding }) - playbackService.init() - const mediaElement = playbackService.player!.media - Object.defineProperties(mediaElement, { - currentTime: { - value: currentTime, - configurable: true - }, - duration: { - value: duration, - configurable: true - } - }) - - const preloadMock = mock(playbackService, 'preload') - mediaElement.dispatchEvent(new Event('timeupdate')) - expect(preloadMock).toHaveBeenCalledTimes(numberOfCalls) - } - ) - }) - - it('registers play', () => { - const recentlyPlayedStoreAddMock = mock(recentlyPlayedStore, 'add') - const recentlyPlayedStoreFetchAllMock = mock(recentlyPlayedStore, 'fetchAll') - const registerPlayMock = mock(songStore, 'registerPlay') - const song = factory('song') - playbackService.registerPlay(song) - expect(recentlyPlayedStoreAddMock).toHaveBeenCalledWith(song) - expect(recentlyPlayedStoreFetchAllMock).toHaveBeenCalled() - expect(registerPlayMock).toHaveBeenCalledWith(song) - expect(song.playCountRegistered).toBe(true) - }) - - it('preloads a song', () => { - const setAttributeMock = jest.fn() - const loadMock = jest.fn() - - const audioElement = { - setAttribute: setAttributeMock, - load: loadMock - } - - const createElementMock = mock(document, 'createElement', audioElement) - mock(songStore, 'getSourceUrl').mockReturnValue('/foo?token=o5afd') - const song = factory('song') - playbackService.preload(song) - expect(createElementMock).toHaveBeenCalledWith('audio') - expect(setAttributeMock).toHaveBeenNthCalledWith(1, 'src', '/foo?token=o5afd') - expect(setAttributeMock).toHaveBeenNthCalledWith(2, 'preload', 'auto') - expect(loadMock).toHaveBeenCalled() - expect(song.preloaded).toBe(true) - }) - - it('restarts a song', async () => { - const song = factory('song') - Object.defineProperty(queueStore, 'current', { - get: () => song - }) - mock(Math, 'floor', 1000) - const emitMock = mock(eventBus, 'emit') - const broadcastMock = mock(socketService, 'broadcast') - const showNotificationMock = mock(playbackService, 'showNotification') - const dataToBroadcast = {} - mock(songStore, 'generateDataToBroadcast', dataToBroadcast) - const restartMock = mock(playbackService.player!, 'restart') - const playMock = mock(window.HTMLMediaElement.prototype, 'play') - - await playbackService.restart() - expect(song.playStartTime).toEqual(1000) - expect(song.playCountRegistered).toBe(false) - expect(emitMock).toHaveBeenCalledWith('SONG_STARTED', song) - expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', dataToBroadcast) - expect(showNotificationMock).toHaveBeenCalled() - expect(restartMock).toHaveBeenCalled() - expect(playMock).toHaveBeenCalled() - }) - - it.each<[RepeatMode, RepeatMode]>([ - ['NO_REPEAT', 'REPEAT_ALL'], - ['REPEAT_ALL', 'REPEAT_ONE'], - ['REPEAT_ONE', 'NO_REPEAT'] - ])( - 'it switches from repeat mode %s to repeat mode %s', - (fromMode, toMode) => { - preferences.repeatMode = fromMode - - playbackService.changeRepeatMode() - expect(preferences.repeatMode).toEqual(toMode) - }) - - it('restarts song if playPrev is triggered after 5 seconds', async () => { - const restartMock = mock(playbackService.player!, 'restart') - Object.defineProperty(playbackService.player!.media, 'currentTime', { - get: () => 6 - }) - Object.defineProperty(queueStore, 'current', { - get: () => factory('song', { length: 120 }) - }) - - await playbackService.playPrev() - expect(restartMock).toHaveBeenCalled() - }) - - it('stops if playPrev is triggered when there is no prev song and repeat mode is NO_REPEAT', async () => { - const stopMock = mock(playbackService, 'stop') - Object.defineProperty(playbackService.player!.media, 'currentTime', { - get: () => 4 - }) - Object.defineProperty(queueStore, 'current', { - get: () => factory('song', { length: 120 }) - }) - Object.defineProperty(playbackService, 'previous', { - get: () => null - }) - preferences.repeatMode = 'NO_REPEAT' - - await playbackService.playPrev() - expect(stopMock).toHaveBeenCalled() - }) - - it('plays the previous song', async () => { - const previousSong = factory('song') - Object.defineProperty(playbackService, 'previous', { - get: () => previousSong - }) - Object.defineProperty(playbackService.player!.media, 'currentTime', { - get: () => 4 - }) - Object.defineProperty(queueStore, 'current', { - get: () => factory('song', { length: 120 }) - }) - const playMock = mock(playbackService, 'play') - - await playbackService.playPrev() - expect(playMock).toHaveBeenCalledWith(previousSong) - }) - - it('stops if playNext is triggered when there is no next song and repeat mode is NO_REPEAT', async () => { - Object.defineProperty(playbackService, 'next', { - get: () => null - }) - preferences.repeatMode = 'NO_REPEAT' - const stopMock = mock(playbackService, 'stop') - - await playbackService.playNext() - expect(stopMock).toHaveBeenCalled() - }) - - it('plays the next song', async () => { - const nextSong = factory('song') - Object.defineProperty(playbackService, 'next', { - get: () => nextSong - }) - const playMock = mock(playbackService, 'play') - - await playbackService.playNext() - expect(playMock).toHaveBeenCalledWith(nextSong) - }) - - it('stops playback', () => { - const currentSong = factory('song') - const pauseMock = mock(playbackService.player!, 'pause') - const seekMock = mock(playbackService.player!, 'seek') - Object.defineProperty(queueStore, 'current', { - get: () => currentSong - }) - const broadcastMock = mock(socketService, 'broadcast') - - playbackService.stop() - expect(currentSong.playbackState).toEqual('Stopped') - expect(pauseMock).toHaveBeenCalled() - expect(seekMock).toHaveBeenCalledWith(0) - expect(broadcastMock).toHaveBeenCalledWith('SOCKET_PLAYBACK_STOPPED') - expect(document.title).toEqual('Koel') - }) - - it('pauses playback', () => { - const currentSong = factory('song') - Object.defineProperty(queueStore, 'current', { - get: () => currentSong - }) - const dataToBroadcast = {} - mock(songStore, 'generateDataToBroadcast', dataToBroadcast) - const pauseMock = mock(playbackService.player!, 'pause') - const broadcastMock = mock(socketService, 'broadcast') - - playbackService.pause() - expect(currentSong.playbackState).toEqual('Paused') - expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', dataToBroadcast) - expect(pauseMock).toHaveBeenCalled() - }) - - it('resumes playback', async () => { - const currentSong = factory('song') - Object.defineProperty(queueStore, 'current', { - get: () => currentSong - }) - const dataToBroadcast = {} - mock(songStore, 'generateDataToBroadcast', dataToBroadcast) - const playMock = mock(window.HTMLMediaElement.prototype, 'play') - const broadcastMock = mock(socketService, 'broadcast') - const emitMock = mock(eventBus, 'emit') - - playbackService.init() - await playbackService.resume() - expect(queueStore.current?.playbackState).toEqual('Playing') - expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', dataToBroadcast) - expect(playMock).toHaveBeenCalled() - expect(emitMock).toHaveBeenCalledWith('SONG_STARTED', currentSong) - }) - - it('plays first in queue if toggled when there is no current song', async () => { - const playFirstInQueueMock = mock(playbackService, 'playFirstInQueue') - Object.defineProperty(queueStore, 'current', { - get: () => null - }) - await playbackService.toggle() - expect(playFirstInQueueMock).toHaveBeenCalled() - }) - - it.each<[FunctionPropertyNames, PlaybackState]>([ - ['resume', 'Stopped'], - ['resume', 'Paused'], - ['pause', 'Playing'] - ])('%ss playback if toggled when current song playback state is %s', async (action, playbackState) => { - const actionMock = mock(playbackService, action) - Object.defineProperty(queueStore, 'current', { - get: () => factory('song', { playbackState }) - }) - await playbackService.toggle() - expect(actionMock).toHaveBeenCalled() - }) - - it('queues and plays all songs shuffled by default', async () => { - const allSongs = factory('song', 5) - const shuffledSongs = factory('song', 5) - Object.defineProperty(songStore, 'all', { - get: () => allSongs - }) - - const firstSongInQueue = factory('song') - Object.defineProperty(queueStore, 'first', { - get: () => firstSongInQueue - }) - - const replaceQueueMock = mock(queueStore, 'replaceQueueWith') - const goMock = mock(router, 'go') - const playMock = mock(playbackService, 'play') - ;(shuffle as jest.Mock).mockReturnValue(shuffledSongs) - - await playbackService.queueAndPlay() - await Vue.nextTick() - expect(shuffle).toHaveBeenCalledWith(allSongs) - expect(replaceQueueMock).toHaveBeenCalledWith(shuffledSongs) - expect(goMock).toHaveBeenCalledWith('queue') - expect(playMock).toHaveBeenCalledWith(firstSongInQueue) - }) - - it('queues and plays songs without shuffling', async () => { - const songs = factory('song', 5) - const replaceQueueMock = mock(queueStore, 'replaceQueueWith') - const goMock = mock(router, 'go') - const playMock = mock(playbackService, 'play') - const firstSongInQueue = songs[0] - Object.defineProperty(queueStore, 'first', { - get: () => firstSongInQueue - }) - - await playbackService.queueAndPlay(songs) - await Vue.nextTick() - expect(shuffle).not.toHaveBeenCalled() - expect(replaceQueueMock).toHaveBeenCalledWith(songs) - expect(goMock).toHaveBeenCalledWith('queue') - expect(playMock).toHaveBeenCalledWith(firstSongInQueue) - }) - - it('queues and plays songs with shuffling', async () => { - const songs = factory('song', 5) - const shuffledSongs = factory('song', 5) - const replaceQueueMock = mock(queueStore, 'replaceQueueWith') - const goMock = mock(router, 'go') - const playMock = mock(playbackService, 'play') - const firstSongInQueue = songs[0] - Object.defineProperty(queueStore, 'first', { - get: () => firstSongInQueue - }) - ;(shuffle as jest.Mock).mockReturnValue(shuffledSongs) - - await playbackService.queueAndPlay(songs, true) - await Vue.nextTick() - expect(shuffle).toHaveBeenCalledWith(songs) - expect(replaceQueueMock).toHaveBeenCalledWith(shuffledSongs) - expect(goMock).toHaveBeenCalledWith('queue') - expect(playMock).toHaveBeenCalledWith(firstSongInQueue) - }) - - it('plays first song in queue', async () => { - const songs = factory('song', 5) - queueStore.all = songs - Object.defineProperty(queueStore, 'first', { - get: () => songs[0] - }) - const playMock = mock(playbackService, 'play') - - await playbackService.playFirstInQueue() - expect(playMock).toHaveBeenCalledWith(songs[0]) - }) - - it('playFirstInQueue triggers queueAndPlay if queue is empty', async () => { - queueStore.all = [] - const queueAndPlayMock = mock(playbackService, 'queueAndPlay') - - await playbackService.playFirstInQueue() - expect(queueAndPlayMock).toHaveBeenCalled() - }) - - it('plays all songs by an artist, shuffled', async () => { - const artist = factory('artist', { - songs: factory('song', 5) - }) - const queueAndPlayMock = mock(playbackService, 'queueAndPlay') - - await playbackService.playAllByArtist(artist) - expect(queueAndPlayMock).toHaveBeenCalledWith(artist.songs, true) - }) - - it('plays all songs by an artist in proper order', async () => { - const artist = factory('artist', { - songs: factory('song', 5) - }) - const orderedSongs = factory('song', 5) - ;(orderBy as jest.Mock).mockReturnValue(orderedSongs) - - const queueAndPlayMock = mock(playbackService, 'queueAndPlay') - await playbackService.playAllByArtist(artist, false) - expect(orderBy).toHaveBeenCalledWith(artist.songs, ['album_id', 'disc', 'track']) - expect(queueAndPlayMock).toHaveBeenCalledWith(orderedSongs) - }) - - it('plays all songs in an album, shuffled', async () => { - const album = factory('album', { - songs: factory('song', 5) - }) - const queueAndPlayMock = mock(playbackService, 'queueAndPlay') - - await playbackService.playAllInAlbum(album) - expect(queueAndPlayMock).toHaveBeenCalledWith(album.songs, true) - }) - - it('plays all songs in an album in proper order', async () => { - const album = factory('album', { - songs: factory('song', 5) - }) - const orderedSongs = factory('song', 5) - ;(orderBy as jest.Mock).mockReturnValue(orderedSongs) - - const queueAndPlayMock = mock(playbackService, 'queueAndPlay') - await playbackService.playAllInAlbum(album, false) - expect(orderBy).toHaveBeenCalledWith(album.songs, ['disc', 'track']) - expect(queueAndPlayMock).toHaveBeenCalledWith(orderedSongs) - }) -}) diff --git a/resources/assets/js/services/playbackService.spec.ts b/resources/assets/js/services/playbackService.spec.ts new file mode 100644 index 00000000..cb661438 --- /dev/null +++ b/resources/assets/js/services/playbackService.spec.ts @@ -0,0 +1,475 @@ +import plyr from 'plyr' +import lodash from 'lodash' +import { expect, it, vi } from 'vitest' +import { eventBus, noop } from '@/utils' +import router from '@/router' +import factory from '@/__tests__/factory' +import UnitTestCase from '@/__tests__/UnitTestCase' +import { nextTick } from 'vue' +import { socketService } from '@/services' +import { playbackService } from './playbackService' + +import { + commonStore, + preferenceStore as preferences, + queueStore, + recentlyPlayedStore, + songStore, + userStore +} from '@/stores' + +new class extends UnitTestCase { + private setupEnvironment () { + document.body.innerHTML = ` +
+
+ + ` + + window.AudioContext = vi.fn().mockImplementation(() => ({ + createMediaElementSource: vi.fn(noop) + })) + } + + protected beforeEach () { + super.beforeEach(() => this.setupEnvironment()) + } + + protected test () { + it('only initializes once', () => { + const spy = vi.spyOn(plyr, 'setup') + playbackService.init() + expect(spy).toHaveBeenCalled() + playbackService.init() + expect(spy).toHaveBeenCalledTimes(1) + }) + + it.each<[boolean, boolean, number, number, number]>([ + [false, false, 100, 400, 1], + [true, false, 100, 400, 0], + [false, true, 100, 400, 0], + [false, false, 100, 500, 0] + ])( + 'when playCountRegistered is %s, isTranscoding is %s, current media time is %d, media duration is %d, then registerPlay() should be call %d times', + (playCountRegistered, isTranscoding, currentTime, duration, numberOfCalls) => { + queueStore.current = factory('song', { playCountRegistered }) + this.setReadOnlyProperty(playbackService, 'isTranscoding', isTranscoding) + playbackService.init() + const mediaElement = playbackService.player!.media + // we can't set mediaElement.currentTime|duration directly because they're read-only + Object.defineProperties(mediaElement, { + currentTime: { + value: currentTime, + configurable: true + }, + duration: { + value: duration, + configurable: true + } + }) + + const registerPlayMock = this.mock(playbackService, 'registerPlay') + mediaElement.dispatchEvent(new Event('timeupdate')) + expect(registerPlayMock).toHaveBeenCalledTimes(numberOfCalls) + }) + + it('plays next song if current song is errored', () => { + playbackService.init() + const playNextMock = this.mock(playbackService, 'playNext') + playbackService.player!.media.dispatchEvent(new Event('error')) + expect(playNextMock).toHaveBeenCalled() + }) + + it('scrobbles if current song ends', () => { + queueStore.current = factory('song') + commonStore.state.useLastfm = true + userStore.current = factory('user', { + preferences: { + lastfm_session_key: 'foo' + } + }) + + playbackService.init() + const scrobbleMock = this.mock(songStore, 'scrobble') + playbackService.player!.media.dispatchEvent(new Event('ended')) + expect(scrobbleMock).toHaveBeenCalled() + }) + + it.each<[RepeatMode, number, number]>([['REPEAT_ONE', 1, 0], ['NO_REPEAT', 0, 1], ['REPEAT_ALL', 0, 1]])( + 'when song ends, if repeat mode is %s then restart() is called %d times and playNext() is called %d times', + (repeatMode, restartCalls, playNextCalls) => { + commonStore.state.useLastfm = false // so that no scrobbling is made unnecessarily + preferences.repeatMode = repeatMode + playbackService.init() + const restartMock = this.mock(playbackService, 'restart') + const playNextMock = this.mock(playbackService, 'playNext') + playbackService.player!.media.dispatchEvent(new Event('ended')) + expect(restartMock).toHaveBeenCalledTimes(restartCalls) + expect(playNextMock).toHaveBeenCalledTimes(playNextCalls) + }) + + it.each([ + [false, true, 300, 310, 0], + [true, false, 300, 310, 0], + [false, false, 300, 400, 0], + [false, false, 300, 310, 1] + ])( + 'when next song preloaded is %s, isTranscoding is %s, current media time is %d, media duration is %d, then preload() should be called %d times', + (preloaded, isTranscoding, currentTime, duration, numberOfCalls) => { + queueStore.current = factory('song', { playCountRegistered: true }) // avoid triggering play count logic + this.setReadOnlyProperty(queueStore, 'next', factory('song', { preloaded })) + this.setReadOnlyProperty(playbackService, 'isTranscoding', isTranscoding) + playbackService.init() + + const mediaElement = playbackService.player!.media + + Object.defineProperties(mediaElement, { + currentTime: { + value: currentTime, + configurable: true + }, + duration: { + value: duration, + configurable: true + } + }) + + const preloadMock = this.mock(playbackService, 'preload') + mediaElement.dispatchEvent(new Event('timeupdate')) + + expect(preloadMock).toHaveBeenCalledTimes(numberOfCalls) + } + ) + + it('registers play', () => { + const recentlyPlayedStoreAddMock = this.mock(recentlyPlayedStore, 'add') + const recentlyPlayedStoreFetchAllMock = this.mock(recentlyPlayedStore, 'fetchAll') + const registerPlayMock = this.mock(songStore, 'registerPlay') + const song = factory('song') + + playbackService.registerPlay(song) + + expect(recentlyPlayedStoreAddMock).toHaveBeenCalledWith(song) + expect(recentlyPlayedStoreFetchAllMock).toHaveBeenCalled() + expect(registerPlayMock).toHaveBeenCalledWith(song) + expect(song.playCountRegistered).toBe(true) + }) + + it('preloads a song', () => { + const audioElement = { + setAttribute: vi.fn(), + load: vi.fn() + } + + const createElementMock = this.mock(document, 'createElement', audioElement) + this.mock(songStore, 'getSourceUrl').mockReturnValue('/foo?token=o5afd') + const song = factory('song') + + playbackService.preload(song) + + expect(createElementMock).toHaveBeenCalledWith('audio') + expect(audioElement.setAttribute).toHaveBeenNthCalledWith(1, 'src', '/foo?token=o5afd') + expect(audioElement.setAttribute).toHaveBeenNthCalledWith(2, 'preload', 'auto') + expect(audioElement.load).toHaveBeenCalled() + expect(song.preloaded).toBe(true) + }) + + it('restarts a song', async () => { + const song = factory('song') + queueStore.current = song + this.mock(Math, 'floor', 1000) + const emitMock = this.mock(eventBus, 'emit') + const broadcastMock = this.mock(socketService, 'broadcast') + const showNotificationMock = this.mock(playbackService, 'showNotification') + const dataToBroadcast = {} + this.mock(songStore, 'generateDataToBroadcast', dataToBroadcast) + const restartMock = this.mock(playbackService.player!, 'restart') + const playMock = this.mock(window.HTMLMediaElement.prototype, 'play') + + await playbackService.restart() + + expect(song.playStartTime).toEqual(1000) + expect(song.playCountRegistered).toBe(false) + expect(emitMock).toHaveBeenCalledWith('SONG_STARTED', song) + expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', dataToBroadcast) + expect(showNotificationMock).toHaveBeenCalled() + expect(restartMock).toHaveBeenCalled() + expect(playMock).toHaveBeenCalled() + }) + + it.each<[RepeatMode, RepeatMode]>([ + ['NO_REPEAT', 'REPEAT_ALL'], + ['REPEAT_ALL', 'REPEAT_ONE'], + ['REPEAT_ONE', 'NO_REPEAT'] + ])('it switches from repeat mode %s to repeat mode %s', (fromMode, toMode) => { + preferences.repeatMode = fromMode + playbackService.changeRepeatMode() + + expect(preferences.repeatMode).toEqual(toMode) + }) + + it('restarts song if playPrev is triggered after 5 seconds', async () => { + const mock = this.mock(playbackService.player!, 'restart') + this.setReadOnlyProperty(playbackService.player!.media, 'currentTime', 6) + queueStore.current = factory('song', { length: 120 }) + + await playbackService.playPrev() + + expect(mock).toHaveBeenCalled() + }) + + it('stops if playPrev is triggered when there is no prev song and repeat mode is NO_REPEAT', async () => { + const stopMock = this.mock(playbackService, 'stop') + this.setReadOnlyProperty(playbackService.player!.media, 'currentTime', 4) + this.setReadOnlyProperty(playbackService, 'previous', undefined) + queueStore.current = factory('song') + preferences.repeatMode = 'NO_REPEAT' + + await playbackService.playPrev() + + expect(stopMock).toHaveBeenCalled() + }) + + it('plays the previous song', async () => { + const previousSong = factory('song') + this.setReadOnlyProperty(playbackService.player!.media, 'currentTime', 4) + this.setReadOnlyProperty(playbackService, 'previous', previousSong) + queueStore.current = factory('song') + const playMock = this.mock(playbackService, 'play') + + await playbackService.playPrev() + + expect(playMock).toHaveBeenCalledWith(previousSong) + }) + + it('stops if playNext is triggered when there is no next song and repeat mode is NO_REPEAT', async () => { + this.setReadOnlyProperty(playbackService, 'next', undefined) + preferences.repeatMode = 'NO_REPEAT' + const stopMock = this.mock(playbackService, 'stop') + + await playbackService.playNext() + + expect(stopMock).toHaveBeenCalled() + }) + + it('plays the next song', async () => { + const nextSong = factory('song') + this.setReadOnlyProperty(playbackService, 'next', nextSong) + const playMock = this.mock(playbackService, 'play') + + await playbackService.playNext() + + expect(playMock).toHaveBeenCalledWith(nextSong) + }) + + it('stops playback', () => { + const currentSong = factory('song') + queueStore.current = currentSong + const pauseMock = this.mock(playbackService.player!, 'pause') + const seekMock = this.mock(playbackService.player!, 'seek') + const broadcastMock = this.mock(socketService, 'broadcast') + + playbackService.stop() + + expect(currentSong.playbackState).toEqual('Stopped') + expect(pauseMock).toHaveBeenCalled() + expect(seekMock).toHaveBeenCalledWith(0) + expect(broadcastMock).toHaveBeenCalledWith('SOCKET_PLAYBACK_STOPPED') + expect(document.title).toEqual('Koel') + }) + + it('pauses playback', () => { + const currentSong = factory('song') + queueStore.current = currentSong + const dataToBroadcast = {} + this.mock(songStore, 'generateDataToBroadcast', dataToBroadcast) + const pauseMock = this.mock(playbackService.player!, 'pause') + const broadcastMock = this.mock(socketService, 'broadcast') + + playbackService.pause() + + expect(currentSong.playbackState).toEqual('Paused') + expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', dataToBroadcast) + expect(pauseMock).toHaveBeenCalled() + }) + + it('resumes playback', async () => { + const currentSong = factory('song') + queueStore.current = currentSong + const dataToBroadcast = {} + this.mock(songStore, 'generateDataToBroadcast', dataToBroadcast) + const playMock = this.mock(window.HTMLMediaElement.prototype, 'play') + const broadcastMock = this.mock(socketService, 'broadcast') + const emitMock = this.mock(eventBus, 'emit') + + playbackService.init() + await playbackService.resume() + + expect(queueStore.current?.playbackState).toEqual('Playing') + expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', dataToBroadcast) + expect(playMock).toHaveBeenCalled() + expect(emitMock).toHaveBeenCalledWith('SONG_STARTED', currentSong) + }) + + it('plays first in queue if toggled when there is no current song', async () => { + const playFirstInQueueMock = this.mock(playbackService, 'playFirstInQueue') + queueStore.current = undefined + + await playbackService.toggle() + + expect(playFirstInQueueMock).toHaveBeenCalled() + }) + + it.each<[MethodOf, PlaybackState]>([ + ['resume', 'Stopped'], + ['resume', 'Paused'], + ['pause', 'Playing'] + ])('%ss playback if toggled when current song playback state is %s', async (action, playbackState) => { + const actionMock = this.mock(playbackService, action) + queueStore.current = factory('song', { playbackState }) + + await playbackService.toggle() + + expect(actionMock).toHaveBeenCalled() + }) + + it('queues and plays all songs shuffled by default', async () => { + const allSongs = factory('song', 5) + const shuffledSongs = factory('song', 5) + songStore.all = allSongs + const firstSongInQueue = factory('song') + this.setReadOnlyProperty(queueStore, 'first', firstSongInQueue) + const replaceQueueMock = this.mock(queueStore, 'replaceQueueWith') + const goMock = this.mock(router, 'go') + const playMock = this.mock(playbackService, 'play') + const shuffleMock = this.mock(lodash, 'shuffle', shuffledSongs) + + await playbackService.queueAndPlay() + await nextTick() + + expect(shuffleMock).toHaveBeenCalledWith(allSongs) + expect(replaceQueueMock).toHaveBeenCalledWith(shuffledSongs) + expect(goMock).toHaveBeenCalledWith('queue') + expect(playMock).toHaveBeenCalledWith(firstSongInQueue) + }) + + it('queues and plays songs without shuffling', async () => { + const songs = factory('song', 5) + const replaceQueueMock = this.mock(queueStore, 'replaceQueueWith') + const goMock = this.mock(router, 'go') + const playMock = this.mock(playbackService, 'play') + const firstSongInQueue = songs[0] + const shuffleMock = this.mock(lodash, 'shuffle') + this.setReadOnlyProperty(queueStore, 'first', firstSongInQueue) + + await playbackService.queueAndPlay(songs) + await nextTick() + + expect(shuffleMock).not.toHaveBeenCalled() + expect(replaceQueueMock).toHaveBeenCalledWith(songs) + expect(goMock).toHaveBeenCalledWith('queue') + expect(playMock).toHaveBeenCalledWith(firstSongInQueue) + }) + + it('queues and plays songs with shuffling', async () => { + const songs = factory('song', 5) + const shuffledSongs = factory('song', 5) + const replaceQueueMock = this.mock(queueStore, 'replaceQueueWith') + const goMock = this.mock(router, 'go') + const playMock = this.mock(playbackService, 'play') + const firstSongInQueue = songs[0] + this.setReadOnlyProperty(queueStore, 'first', firstSongInQueue) + const shuffleMock = this.mock(lodash, 'shuffle', shuffledSongs) + + await playbackService.queueAndPlay(songs, true) + await nextTick() + + expect(shuffleMock).toHaveBeenCalledWith(songs) + expect(replaceQueueMock).toHaveBeenCalledWith(shuffledSongs) + expect(goMock).toHaveBeenCalledWith('queue') + expect(playMock).toHaveBeenCalledWith(firstSongInQueue) + }) + + it('plays first song in queue', async () => { + const songs = factory('song', 5) + queueStore.all = songs + this.setReadOnlyProperty(queueStore, 'first', songs[0]) + const playMock = this.mock(playbackService, 'play') + + await playbackService.playFirstInQueue() + + expect(playMock).toHaveBeenCalledWith(songs[0]) + }) + + it('playFirstInQueue triggers queueAndPlay if queue is empty', async () => { + queueStore.all = [] + const queueAndPlayMock = this.mock(playbackService, 'queueAndPlay') + + await playbackService.playFirstInQueue() + + expect(queueAndPlayMock).toHaveBeenCalled() + }) + + it('plays all songs by an artist, shuffled', async () => { + const artist = factory('artist', { + songs: factory('song', 5) + }) + + const queueAndPlayMock = this.mock(playbackService, 'queueAndPlay') + + await playbackService.playAllByArtist(artist) + + expect(queueAndPlayMock).toHaveBeenCalledWith(artist.songs, true) + }) + + it('plays all songs by an artist in proper order', async () => { + const artist = factory('artist', { + songs: factory('song', 5) + }) + + const orderedSongs = factory('song', 5) + const orderByMock = this.mock(lodash, 'orderBy', orderedSongs) + const queueAndPlayMock = this.mock(playbackService, 'queueAndPlay') + + await playbackService.playAllByArtist(artist, false) + + expect(orderByMock).toHaveBeenCalledWith(artist.songs, ['album_id', 'disc', 'track']) + expect(queueAndPlayMock).toHaveBeenCalledWith(orderedSongs) + }) + + it('plays all songs in an album, shuffled', async () => { + const album = factory('album', { + songs: factory('song', 5) + }) + + const queueAndPlayMock = this.mock(playbackService, 'queueAndPlay') + + await playbackService.playAllInAlbum(album) + + expect(queueAndPlayMock).toHaveBeenCalledWith(album.songs, true) + }) + + it('plays all songs in an album in proper order', async () => { + const album = factory('album', { + songs: factory('song', 5) + }) + + const orderedSongs = factory('song', 5) + const orderByMock = this.mock(lodash, 'orderBy', orderedSongs) + const queueAndPlayMock = this.mock(playbackService, 'queueAndPlay') + + await playbackService.playAllInAlbum(album, false) + + expect(orderByMock).toHaveBeenCalledWith(album.songs, ['disc', 'track']) + expect(queueAndPlayMock).toHaveBeenCalledWith(orderedSongs) + }) + } +} diff --git a/resources/assets/js/services/playbackService.ts b/resources/assets/js/services/playbackService.ts index f8266561..ac4ba664 100644 --- a/resources/assets/js/services/playbackService.ts +++ b/resources/assets/js/services/playbackService.ts @@ -30,7 +30,6 @@ export const playbackService = { volumeInput: null as unknown as HTMLInputElement, repeatModes: REPEAT_MODES, initialized: false, - mainWin: null as any, init () { // We don't need to init this service twice, or the media events will be duplicated. @@ -38,7 +37,7 @@ export const playbackService = { return } - this.player = plyr.setup(document.querySelector('.plyr')!, { + this.player = plyr.setup('.plyr', { controls: [] })[0] @@ -83,6 +82,10 @@ export const playbackService = { }, setMediaSessionActionHandlers () { + if (!navigator.mediaSession) { + return + } + navigator.mediaSession.setActionHandler('play', () => this.resume()) navigator.mediaSession.setActionHandler('pause', () => this.pause()) navigator.mediaSession.setActionHandler('previoustrack', () => this.playPrev()) @@ -100,7 +103,7 @@ export const playbackService = { preferences.repeatMode === 'REPEAT_ONE' ? this.restart() : this.playNext() }) - mediaElement.addEventListener('timeupdate', throttle(() => { + let timeUpdateHandler = () => { const currentSong = queueStore.current! if (!currentSong.playCountRegistered && !this.isTranscoding) { @@ -120,7 +123,13 @@ export const playbackService = { if (mediaElement.duration && mediaElement.currentTime + PRELOAD_BUFFER > mediaElement.duration) { this.preload(nextSong) } - }, 3000)) + } + + if (process.env.NODE_ENV !== 'test') { + timeUpdateHandler = throttle(timeUpdateHandler, 3000) + } + + mediaElement.addEventListener('timeupdate', timeUpdateHandler) }, get isTranscoding () { @@ -150,7 +159,7 @@ export const playbackService = { * So many dreams swinging out of the blue * We'll let them come true */ - async play (song: Song | undefined) { + async play (song?: Song) { if (!song) { return } @@ -184,14 +193,14 @@ export const playbackService = { } try { - const notif = new window.Notification(`♫ ${song.title}`, { + const notification = new window.Notification(`♫ ${song.title}`, { icon: song.album.cover, body: `${song.album.name} – ${song.artist.name}` }) - notif.onclick = () => window.focus() + notification.onclick = () => window.focus() - window.setTimeout(() => notif.close(), 5000) + window.setTimeout(() => notification.close(), 5000) } catch (e) { // Notification fails. // @link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification @@ -203,7 +212,11 @@ export const playbackService = { artist: song.artist.name, album: song.album.name, artwork: [ - { src: song.album.cover, sizes: '256x256', type: 'image/png' } + { + src: song.album.cover, + sizes: '256x256', + type: 'image/png' + } ] }) }, @@ -250,7 +263,7 @@ export const playbackService = { * The previous song in the queue. * If we're in REPEAT_ALL mode and there's no prev song, get the last song. */ - get previous (): Song | undefined { + get previous () { if (queueStore.previous) { return queueStore.previous } @@ -279,7 +292,6 @@ export const playbackService = { * If the prev song is not found and the current mode is NO_REPEAT, we stop completely. */ async playPrev () { - console.log('called') // If the song's duration is greater than 5 seconds and we've passed 5 seconds into it, // restart playing instead. if (this.getPlayer().media.currentTime > 5 && queueStore.current!.length > 5) { diff --git a/resources/assets/js/types.d.ts b/resources/assets/js/types.d.ts index 57133a74..c8b4dc47 100644 --- a/resources/assets/js/types.d.ts +++ b/resources/assets/js/types.d.ts @@ -62,7 +62,7 @@ interface Plyr { } declare module 'plyr' { - function setup (el: HTMLMediaElement | HTMLMediaElement[], options: Record): Plyr[] + function setup (el: string | HTMLMediaElement | HTMLMediaElement[], options: Record): Plyr[] } declare module 'ismobilejs' { diff --git a/vite.config.js b/vite.config.js index d9bfafc9..e00c171d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -25,7 +25,7 @@ export default defineConfig({ KOEL_ENV: '""' }, test: { - environment: 'happy-dom', + environment: 'jsdom', setupFiles: path.resolve(__dirname, './resources/assets/js/__tests__/setup.ts') }, }) diff --git a/yarn.lock b/yarn.lock index 36379fe1..95acba20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1108,6 +1108,11 @@ "@testing-library/dom" "^8.5.0" "@vue/test-utils" "^2.0.0-rc.18" +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" @@ -1203,13 +1208,6 @@ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== -"@types/concat-stream@^1.6.0": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.1.tgz#24bcfc101ecf68e886aaedce60dfd74b632a1b74" - integrity sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA== - dependencies: - "@types/node" "*" - "@types/connect-history-api-fallback@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" @@ -1265,13 +1263,6 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/form-data@0.0.33": - version "0.0.33" - resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-0.0.33.tgz#c9ac85b2a5fd18435b8c85d9ecb50e6d6c893ff8" - integrity sha1-yayFsqX9GENbjIXZ7LUObWyJP/g= - dependencies: - "@types/node" "*" - "@types/glob@^7.1.1": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -1358,21 +1349,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c" integrity sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA== -"@types/node@^10.0.3": - version "10.17.60" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" - integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== - "@types/node@^14.14.31": version "14.18.13" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.13.tgz#6ad4d9db59e6b3faf98dcfe4ca9d2aec84443277" integrity sha512-Z6/KzgyWOga3pJNS42A+zayjhPbf2zM3hegRQaOPnLOzEi86VV++6FLDWgR1LGrVCRufP/ph2daa3tEa5br1zA== -"@types/node@^8.0.0": - version "8.10.66" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" - integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== - "@types/nprogress@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@types/nprogress/-/nprogress-0.2.0.tgz#86c593682d4199212a0509cc3c4d562bbbd6e45f" @@ -1388,7 +1369,7 @@ resolved "https://registry.yarnpkg.com/@types/pusher-js/-/pusher-js-4.2.2.tgz#129fae1854255c5883e874137cd045c48d0a422a" integrity sha512-LP9isBRAFlNzQohQtySJxJjzmy4zQCcv5xGZD2G3rsDnTWfpEkFKyLw3x9711pFAXwwUl9ZivxKkcnFr8umSAQ== -"@types/qs@*", "@types/qs@^6.2.31": +"@types/qs@*": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== @@ -1822,6 +1803,11 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +abab@^2.0.5, abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1835,6 +1821,14 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + acorn-import-assertions@^1.7.6: version "1.8.0" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" @@ -1845,6 +1839,16 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + acorn@^8.4.1, acorn@^8.5.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" @@ -1866,6 +1870,13 @@ adjust-sourcemap-loader@2.0.0: object-path "0.11.4" regex-parser "2.2.10" +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + aggregate-error@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" @@ -2053,11 +2064,6 @@ array.prototype.flat@^1.2.5: es-abstract "^1.19.2" es-shim-unscopables "^1.0.0" -asap@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= - asn1.js@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" @@ -2343,6 +2349,11 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -2536,7 +2547,7 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001317: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz#39476d3aa8d83ea76359c70302eafdd4a1d727dd" integrity sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw== -caseless@^0.12.0, caseless@~0.12.0: +caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= @@ -2792,7 +2803,7 @@ colors@~1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM= -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -2871,16 +2882,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.6.0, concat-stream@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - concat@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/concat/-/concat-1.0.3.tgz#40f3353089d65467695cb1886b45edd637d8cca8" @@ -3163,11 +3164,6 @@ css-what@^6.0.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== -css.escape@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" - integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= - css@^2.0.0: version "2.2.4" resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" @@ -3285,6 +3281,23 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + csstype@^2.6.8: version "2.6.20" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda" @@ -3353,6 +3366,15 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-urls@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + dayjs@^1.10.4: version "1.11.0" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.0.tgz#009bf7ef2e2ea2d5db2e6583d2d39a4b5061e805" @@ -3365,6 +3387,13 @@ debug@2.6.9, debug@^2.6.9: dependencies: ms "2.0.0" +debug@4, debug@^4.1.0, debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" @@ -3379,13 +3408,6 @@ debug@^3.1.0, debug@^3.1.1, debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -3398,6 +3420,11 @@ decamelize@^1.1.2: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -3420,6 +3447,11 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -3543,6 +3575,13 @@ domelementtype@^2.0.1, domelementtype@^2.2.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + domhandler@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a" @@ -3908,6 +3947,18 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-import-resolver-node@^0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" @@ -4085,6 +4136,11 @@ esprima@^2.6.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" @@ -4324,7 +4380,7 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= @@ -4491,13 +4547,13 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@^2.2.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" - combined-stream "^1.0.6" + combined-stream "^1.0.8" mime-types "^2.1.12" form-data@~2.3.2: @@ -4602,11 +4658,6 @@ get-own-enumerable-property-symbols@^3.0.0: resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== -get-port@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" - integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= - get-stream@^5.0.0, get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" @@ -4768,19 +4819,6 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -happy-dom@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-3.1.0.tgz#dad86056ac2ee3d5b4172fba41fe3b4a4e1cea4b" - integrity sha512-BewZQwLdu6JS9HYT7enB2toju80OjSjl44+3HXMB3hT+2skC9Mja+/N/b+SbtnwJCMbQqiZVzy/RXevPPuBIXQ== - dependencies: - css.escape "^1.5.1" - he "^1.2.0" - node-fetch "^2.x.x" - sync-request "^6.1.0" - webidl-conversions "^7.0.0" - whatwg-encoding "^2.0.0" - whatwg-mimetype "^3.0.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -4883,6 +4921,13 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + html-entities@^2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" @@ -4921,16 +4966,6 @@ htmlparser2@^4.1.0: domutils "^2.0.0" entities "^2.0.0" -http-basic@^8.1.1: - version "8.1.3" - resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-8.1.3.tgz#a7cabee7526869b9b710136970805b1004261bbf" - integrity sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw== - dependencies: - caseless "^0.12.0" - concat-stream "^1.6.2" - http-response-object "^3.0.1" - parse-cache-control "^1.0.1" - http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -4962,6 +4997,15 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.6.tgz#2e02406ab2df8af8a7abfba62e0da01c62b95afd" integrity sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA== +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + http-proxy-middleware@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz#03af0f4676d172ae775cb5c33f592f40e1a4e07a" @@ -4982,13 +5026,6 @@ http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" -http-response-object@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810" - integrity sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA== - dependencies: - "@types/node" "^10.0.3" - http-signature@~1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" @@ -5003,6 +5040,14 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -5345,6 +5390,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -5512,6 +5562,39 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= +jsdom@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-19.0.0.tgz#93e67c149fe26816d38a849ea30ac93677e16b6a" + integrity sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A== + dependencies: + abab "^2.0.5" + acorn "^8.5.0" + acorn-globals "^6.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.1" + decimal.js "^10.3.1" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^10.0.0" + ws "^8.2.3" + xml-name-validator "^4.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -5687,6 +5770,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + lilconfig@^2.0.3, lilconfig@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" @@ -6135,13 +6226,6 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-fetch@^2.x.x: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - node-forge@^1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" @@ -6254,6 +6338,11 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -6338,6 +6427,18 @@ opencollective-postinstall@^2.0.2: resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -6449,11 +6550,6 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-cache-control@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e" - integrity sha1-juqz5U+laSD+Fro493+iGqzC104= - parse-json@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" @@ -6464,6 +6560,11 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -7154,6 +7255,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + prepend-http@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -7197,13 +7303,6 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -promise@^8.0.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" - integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== - dependencies: - asap "~2.0.6" - proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -7234,7 +7333,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.28: +psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== @@ -7294,13 +7393,6 @@ qs@6.9.7: resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== -qs@^6.4.0: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - qs@~6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" @@ -7359,7 +7451,7 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6: +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -7667,6 +7759,13 @@ sax@~1.2.1: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + schema-utils@^0.4.5: version "0.4.7" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" @@ -8291,21 +8390,10 @@ svgo@^2.7.0: picocolors "^1.0.0" stable "^0.1.8" -sync-request@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.1.0.tgz#e96217565b5e50bbffe179868ba75532fb597e68" - integrity sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw== - dependencies: - http-response-object "^3.0.1" - sync-rpc "^1.2.1" - then-request "^6.0.0" - -sync-rpc@^1.2.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/sync-rpc/-/sync-rpc-1.3.6.tgz#b2e8b2550a12ccbc71df8644810529deb68665a7" - integrity sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw== - dependencies: - get-port "^3.1.0" +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== tapable@^2.1.1, tapable@^2.2.0: version "2.2.1" @@ -8347,23 +8435,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -then-request@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/then-request/-/then-request-6.0.2.tgz#ec18dd8b5ca43aaee5cb92f7e4c1630e950d4f0c" - integrity sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA== - dependencies: - "@types/concat-stream" "^1.6.0" - "@types/form-data" "0.0.33" - "@types/node" "^8.0.0" - "@types/qs" "^6.2.31" - caseless "~0.12.0" - concat-stream "^1.6.0" - form-data "^2.2.0" - http-basic "^8.1.1" - http-response-object "^3.0.1" - promise "^8.0.0" - qs "^6.4.0" - throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" @@ -8425,6 +8496,15 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -8433,10 +8513,12 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" ts-loader@^9.3.0: version "9.3.0" @@ -8521,6 +8603,13 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -8549,11 +8638,6 @@ type@^1.0.1: resolved "https://registry.yarnpkg.com/type/-/type-1.0.3.tgz#16f5d39f27a2d28d86e48f8981859e9d3296c179" integrity sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg== -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - typescript@^4.6.3: version "4.6.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" @@ -8602,6 +8686,11 @@ uniqs@^2.0.0: resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= +universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -8766,6 +8855,20 @@ vue@^3.2.32: "@vue/server-renderer" "3.2.32" "@vue/shared" "3.2.32" +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923" + integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg== + dependencies: + xml-name-validator "^4.0.0" + wait-on@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.0.tgz#7e9bf8e3d7fe2daecbb7a570ac8ca41e9311c7e7" @@ -8792,11 +8895,6 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= - webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -8966,13 +9064,21 @@ whatwg-mimetype@^3.0.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= +whatwg-url@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-10.0.0.tgz#37264f720b575b4a311bd4094ed8c760caaa05da" + integrity sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w== dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" whet.extend@~0.9.9: version "0.9.9" @@ -9007,7 +9113,7 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== -word-wrap@^1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== @@ -9035,11 +9141,26 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +ws@^8.2.3: + version "8.6.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23" + integrity sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw== + ws@^8.4.2: version "8.5.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xmlhttprequest@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"