fix: harden node upgrades and prepare 1.9.0-alpha
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import { useCloudStore } from '@/stores/cloud'
|
||||
import { useUploadNotifications } from '../useUploadNotifications'
|
||||
import UploadProgress from '@/components/cloud/UploadProgress.vue'
|
||||
const action = vi.hoisted(() => vi.fn())
|
||||
vi.mock('../useToast', () => ({ useToast: () => ({ action }) }))
|
||||
const origin = '/dashboard/cloud/documents?path=/uploads'
|
||||
async function setup() {
|
||||
const pinia = createPinia(); setActivePinia(pinia)
|
||||
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/:pathMatch(.*)*', component: { template: '<div />' } }] })
|
||||
await router.push(origin); await router.isReady()
|
||||
const wrapper = mount(defineComponent({ components: { UploadProgress }, setup() { useUploadNotifications() }, template: '<UploadProgress />' }), { global: { plugins: [pinia, router] } })
|
||||
const store = useCloudStore()
|
||||
store.upload = { active: true, paused: false, filename: 'photo.png', destination: '/uploads', originRoute: origin, sent: 100, total: 100, completed: 0, count: 1, error: null, cancelled: false }
|
||||
await nextTick()
|
||||
return { store, router, wrapper }
|
||||
}
|
||||
beforeEach(() => action.mockClear())
|
||||
describe('upload screen and completion notification', () => {
|
||||
it('shows Saving until the server finishes, then Complete with dismiss on the origin only', async () => {
|
||||
const { store, wrapper } = await setup()
|
||||
expect(wrapper.text()).toContain('Saving photo.png'); expect(action).not.toHaveBeenCalled()
|
||||
store.upload!.completed = 1; store.upload!.active = false; await nextTick()
|
||||
expect(wrapper.text()).toContain('Complete · photo.png'); expect(action).not.toHaveBeenCalled()
|
||||
await wrapper.get('button[aria-label="Dismiss upload"]').trigger('click')
|
||||
expect(store.upload).toBeNull(); wrapper.unmount()
|
||||
})
|
||||
it('hides progress on another screen, notifies once on completion and returns to the origin', async () => {
|
||||
const { store, router, wrapper } = await setup()
|
||||
await router.push('/dashboard/apps'); expect(wrapper.find('[role="status"]').exists()).toBe(false)
|
||||
expect(store.upload!.active).toBe(true)
|
||||
store.upload!.completed = 1; store.upload!.active = false; await nextTick()
|
||||
expect(action).toHaveBeenCalledTimes(1)
|
||||
expect(action.mock.calls[0]![0]).toBe('Upload complete · photo.png')
|
||||
action.mock.calls[0]![1].onClick(); await router.isReady(); await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(router.currentRoute.value.fullPath).toBe(origin)
|
||||
expect(wrapper.text()).toContain('Complete · photo.png')
|
||||
await router.push('/dashboard'); expect(action).toHaveBeenCalledTimes(1); wrapper.unmount()
|
||||
})
|
||||
it('also hides on other Cloud folders and shows progress again on return', async () => {
|
||||
const { router, wrapper } = await setup()
|
||||
await router.push('/dashboard/cloud/documents?path=/different')
|
||||
expect(wrapper.find('[role="status"]').exists()).toBe(false)
|
||||
await router.push(origin); expect(wrapper.text()).toContain('Saving'); wrapper.unmount()
|
||||
})
|
||||
it.each(['error', 'cancelled'] as const)('never calls a %s outcome complete', async field => {
|
||||
const { store, router, wrapper } = await setup(); await router.push('/dashboard')
|
||||
if (field === 'error') store.upload!.error = 'No space'; else store.upload!.cancelled = true
|
||||
store.upload!.active = false; await nextTick()
|
||||
expect(action).toHaveBeenCalledTimes(1); expect(action.mock.calls[0]![0]).not.toContain('complete')
|
||||
expect(action.mock.calls[0]![0]).toContain('0/1 saved'); wrapper.unmount()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import { watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useCloudStore } from '@/stores/cloud'
|
||||
import { useToast } from './useToast'
|
||||
|
||||
/** Keep progress on its originating screen; announce completion elsewhere once. */
|
||||
export function useUploadNotifications() {
|
||||
const store = useCloudStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
watch(() => store.upload?.active, (active, wasActive) => {
|
||||
const task = store.upload
|
||||
if (wasActive !== true || active !== false || !task || route.fullPath === task.originRoute) return
|
||||
const message = task.error
|
||||
? `Upload failed · ${task.completed}/${task.count} saved: ${task.error}`
|
||||
: task.cancelled
|
||||
? `Upload stopped · ${task.completed}/${task.count} saved`
|
||||
: `Upload complete · ${task.count === 1 ? task.filename : `${task.count} files`}`
|
||||
toast.action(message, {
|
||||
label: 'View upload',
|
||||
onClick: () => { void router.push(task.originRoute || '/dashboard/cloud') },
|
||||
}, { variant: task.error ? 'error' : task.cancelled ? 'info' : 'success', duration: 8000 })
|
||||
}, { flush: 'sync' })
|
||||
}
|
||||
Reference in New Issue
Block a user