d11ddff912
CI / test (push) Has been cancelled
Frame settings now offer two update-frequency modes: "at specific times" or "every X minutes". Times are stored as an int[] of minutes-since-midnight, allowing multiple slots per day at minute granularity. Backend computes the earliest upcoming slot for X-Interval-Ms and uses the most-recent-past slot as the rotation-due boundary. PWA settings sheet has hour/minute/AM-PM dropdowns with + Add / trash, a live "next update" preview, and a note that changes only take effect at the device's next sync. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
513 lines
20 KiB
TypeScript
513 lines
20 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { mount, flushPromises } from '@vue/test-utils'
|
|
import { setActivePinia, createPinia } from 'pinia'
|
|
import UploadView from '@/views/UploadView.vue'
|
|
import { useUploadStore } from '@/stores/upload'
|
|
import { useDevicesStore } from '@/stores/devices'
|
|
import { useImagesStore } from '@/stores/images'
|
|
import type { Device } from '@/types'
|
|
|
|
const routerReplace = vi.fn()
|
|
|
|
vi.mock('@/components/CropEditor.vue', () => ({
|
|
default: {
|
|
name: 'CropEditor',
|
|
template: '<div class="crop-editor-stub" />',
|
|
props: ['src', 'orientation', 'deviceName', 'initialParams', 'initialOrientation'],
|
|
emits: ['crop'],
|
|
},
|
|
}))
|
|
vi.mock('@/components/StickerCanvas.vue', () => ({
|
|
default: {
|
|
name: 'StickerCanvas',
|
|
template: '<div class="sticker-canvas-stub" />',
|
|
props: ['croppedUrl', 'orientation', 'stickers'],
|
|
emits: ['add-sticker', 'update-sticker', 'remove-sticker', 'done'],
|
|
},
|
|
}))
|
|
vi.mock('@/components/DevicePicker.vue', () => ({
|
|
default: {
|
|
name: 'DevicePicker',
|
|
template: '<div class="device-picker-stub" />',
|
|
props: ['modelValue', 'devices', 'selected', 'uploading'],
|
|
emits: ['update:modelValue', 'update:selected', 'confirm'],
|
|
},
|
|
}))
|
|
vi.mock('@/components/BaseButton.vue', () => ({
|
|
default: {
|
|
name: 'BaseButton',
|
|
template: '<button @click="$emit(\'click\')"><slot /></button>',
|
|
props: ['variant', 'disabled'],
|
|
emits: ['click'],
|
|
},
|
|
}))
|
|
vi.mock('vue-router', () => ({
|
|
useRouter: () => ({ replace: routerReplace, push: vi.fn() }),
|
|
}))
|
|
|
|
const toastShow = vi.fn()
|
|
vi.mock('@/stores/toast', () => ({
|
|
useToastStore: () => ({ show: toastShow }),
|
|
}))
|
|
|
|
const makeDevice = (overrides: Partial<Device> = {}): Device => ({
|
|
id: 1,
|
|
mac: 'AA',
|
|
name: 'Living Room',
|
|
orientation: 'landscape',
|
|
rotationIntervalMinutes: 60,
|
|
wakeTimes: [],
|
|
timezone: 'UTC',
|
|
uniquenessWindow: 30,
|
|
linkedAt: '2026-01-01T00:00:00Z',
|
|
lastSeenAt: null,
|
|
lockedImageId: null,
|
|
currentImageId: null,
|
|
...overrides,
|
|
})
|
|
|
|
function primeUploadStore(file?: File) {
|
|
const upload = useUploadStore()
|
|
upload.originalFile = file ?? new File(['x'], 'orig.jpg', { type: 'image/jpeg' })
|
|
upload.originalUrl = 'blob:original'
|
|
return upload
|
|
}
|
|
|
|
describe('UploadView', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia())
|
|
routerReplace.mockClear()
|
|
toastShow.mockClear()
|
|
vi.unstubAllGlobals()
|
|
vi.stubGlobal('URL', {
|
|
createObjectURL: vi.fn(() => 'blob:mock'),
|
|
revokeObjectURL: vi.fn(),
|
|
})
|
|
})
|
|
|
|
it('redirects to / when no upload is in progress', async () => {
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
mount(UploadView)
|
|
await flushPromises()
|
|
expect(routerReplace).toHaveBeenCalledWith('/')
|
|
})
|
|
|
|
it('starts on the crop step with the original URL passed to CropEditor', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
const crop = wrapper.findComponent({ name: 'CropEditor' })
|
|
expect(crop.exists()).toBe(true)
|
|
expect(crop.props('src')).toBe('blob:original')
|
|
expect(wrapper.text()).toContain('Crop photo')
|
|
})
|
|
|
|
it('uses "Edit crop" as the step label when editing', async () => {
|
|
const upload = primeUploadStore()
|
|
upload.editingImageId = 7
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('Edit crop')
|
|
})
|
|
|
|
it('falls back to landscape orientation when no devices are loaded', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
expect(wrapper.findComponent({ name: 'CropEditor' }).props('orientation')).toBe('landscape')
|
|
})
|
|
|
|
it('uses the context device orientation when contextDeviceId matches', async () => {
|
|
const upload = primeUploadStore()
|
|
upload.contextDeviceId = 2
|
|
const devices = useDevicesStore()
|
|
devices.devices = [makeDevice({ id: 1, orientation: 'landscape' }), makeDevice({ id: 2, orientation: 'portrait' })]
|
|
vi.spyOn(devices, 'fetchDevices').mockResolvedValue()
|
|
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
expect(wrapper.findComponent({ name: 'CropEditor' }).props('orientation')).toBe('portrait')
|
|
expect(wrapper.findComponent({ name: 'CropEditor' }).props('deviceName')).toBe(devices.devices[1].name)
|
|
})
|
|
|
|
it('uses the first device as the context when no contextDeviceId is set', async () => {
|
|
primeUploadStore()
|
|
const devices = useDevicesStore()
|
|
devices.devices = [makeDevice({ id: 1, orientation: 'portrait' })]
|
|
vi.spyOn(devices, 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
expect(wrapper.findComponent({ name: 'CropEditor' }).props('orientation')).toBe('portrait')
|
|
})
|
|
|
|
it('crop emit advances to stickers step and stores the crop on the upload store', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
|
|
const blob = new Blob(['x'])
|
|
const params = { natX: 0, natY: 0, natW: 100, natH: 50 }
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', { blob, params, orientation: 'landscape' })
|
|
await flushPromises()
|
|
expect(wrapper.findComponent({ name: 'StickerCanvas' }).exists()).toBe(true)
|
|
expect(wrapper.text()).toContain('Add stickers')
|
|
})
|
|
|
|
it('skip on stickers step opens the device picker for new uploads', async () => {
|
|
const upload = primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
const blob = new Blob(['x'])
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', { blob, params: { natX: 0, natY: 0, natW: 1, natH: 1 }, orientation: 'landscape' })
|
|
upload.croppedBlob = blob
|
|
await flushPromises()
|
|
|
|
const skip = wrapper.find('.upload-view__skip')
|
|
await skip.trigger('click')
|
|
await flushPromises()
|
|
expect(wrapper.findComponent({ name: 'DevicePicker' }).props('modelValue')).toBe(true)
|
|
})
|
|
|
|
it('skip on stickers triggers reprocess directly when editing', async () => {
|
|
const upload = primeUploadStore()
|
|
upload.editingImageId = 11
|
|
const devices = useDevicesStore()
|
|
vi.spyOn(devices, 'fetchDevices').mockResolvedValue()
|
|
const images = useImagesStore()
|
|
const reprocess = vi.spyOn(images, 'reprocessImage').mockResolvedValue({ id: 11 } as any)
|
|
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
const blob = new Blob(['x'])
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', { blob, params: { natX: 0, natY: 0, natW: 1, natH: 1 }, orientation: 'landscape' })
|
|
upload.croppedBlob = blob
|
|
await flushPromises()
|
|
|
|
await wrapper.find('.upload-view__skip').trigger('click')
|
|
await flushPromises()
|
|
expect(reprocess).toHaveBeenCalled()
|
|
expect(wrapper.text()).toContain('Photo updated!')
|
|
})
|
|
|
|
it('skip is a no-op when there is no cropped blob yet', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
// Force into stickers without having a croppedBlob
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', { blob: null as any, params: null as any, orientation: 'landscape' })
|
|
// Even if we click skip, no DevicePicker should open
|
|
const skip = wrapper.find('.upload-view__skip')
|
|
if (skip.exists()) await skip.trigger('click')
|
|
expect(wrapper.findComponent({ name: 'DevicePicker' }).props('modelValue')).toBe(false)
|
|
})
|
|
|
|
it('stickers done opens the picker for new uploads', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'StickerCanvas' }).vm.$emit('done', new Blob(['final']))
|
|
await flushPromises()
|
|
expect(wrapper.findComponent({ name: 'DevicePicker' }).props('modelValue')).toBe(true)
|
|
})
|
|
|
|
it('stickers done triggers reprocess directly when editing', async () => {
|
|
const upload = primeUploadStore()
|
|
upload.editingImageId = 22
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const reprocess = vi.spyOn(useImagesStore(), 'reprocessImage').mockResolvedValue({ id: 22 } as any)
|
|
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'StickerCanvas' }).vm.$emit('done', new Blob(['final']))
|
|
await flushPromises()
|
|
expect(reprocess).toHaveBeenCalled()
|
|
expect(wrapper.text()).toContain('Photo updated!')
|
|
})
|
|
|
|
it('back from crop cleans up and routes to /library', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.find('.upload-view__back').trigger('click')
|
|
expect(routerReplace).toHaveBeenCalledWith('/library')
|
|
})
|
|
|
|
it('back from stickers returns to the crop step', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('Add stickers')
|
|
await wrapper.find('.upload-view__back').trigger('click')
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('Crop photo')
|
|
})
|
|
|
|
it('confirm in the device picker uploads, sets approvals, and shows the done step', async () => {
|
|
const upload = primeUploadStore()
|
|
upload.selectedDeviceIds = [1, 2]
|
|
const images = useImagesStore()
|
|
const uploadSpy = vi.spyOn(images, 'uploadImage').mockResolvedValue({ id: 100 } as any)
|
|
const approvalSpy = vi.spyOn(images, 'setApproval').mockResolvedValue()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'StickerCanvas' }).vm.$emit('done', new Blob(['final']))
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'DevicePicker' }).vm.$emit('confirm')
|
|
await flushPromises()
|
|
|
|
expect(uploadSpy).toHaveBeenCalled()
|
|
expect(approvalSpy).toHaveBeenCalledTimes(2)
|
|
expect(wrapper.text()).toContain('Photo added!')
|
|
})
|
|
|
|
it('upload errors surface as a toast', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useImagesStore(), 'uploadImage').mockRejectedValue(new Error('disk full'))
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'StickerCanvas' }).vm.$emit('done', new Blob(['final']))
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'DevicePicker' }).vm.$emit('confirm')
|
|
await flushPromises()
|
|
expect(toastShow).toHaveBeenCalledWith('disk full', 'error')
|
|
})
|
|
|
|
it('falls back to a generic message for non-Error rejections', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useImagesStore(), 'uploadImage').mockRejectedValue('weird')
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'StickerCanvas' }).vm.$emit('done', new Blob(['final']))
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'DevicePicker' }).vm.$emit('confirm')
|
|
await flushPromises()
|
|
expect(toastShow).toHaveBeenCalledWith('Upload failed', 'error')
|
|
})
|
|
|
|
it('Done button on the success step routes to /library', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useImagesStore(), 'uploadImage').mockResolvedValue({ id: 1 } as any)
|
|
vi.spyOn(useImagesStore(), 'setApproval').mockResolvedValue()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'StickerCanvas' }).vm.$emit('done', new Blob(['final']))
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'DevicePicker' }).vm.$emit('confirm')
|
|
await flushPromises()
|
|
|
|
routerReplace.mockClear()
|
|
await wrapper.find('.upload-view__done-btn').trigger('click')
|
|
expect(routerReplace).toHaveBeenCalledWith('/library')
|
|
})
|
|
|
|
it('updates selectedDeviceIds when the picker emits update:selected', async () => {
|
|
const upload = primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'DevicePicker' }).vm.$emit('update:selected', [3, 4])
|
|
expect(upload.selectedDeviceIds).toEqual([3, 4])
|
|
})
|
|
|
|
it('forwards sticker emits to the upload store', async () => {
|
|
const upload = primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
const canvas = wrapper.findComponent({ name: 'StickerCanvas' })
|
|
const sticker = { id: 's1', type: 'emoji', x: 0, y: 0, scale: 1, rotation: 0 } as any
|
|
await canvas.vm.$emit('add-sticker', sticker)
|
|
expect(upload.stickers).toHaveLength(1)
|
|
await canvas.vm.$emit('update-sticker', 's1', { x: 9 })
|
|
expect(upload.stickers[0].x).toBe(9)
|
|
await canvas.vm.$emit('remove-sticker', 's1')
|
|
expect(upload.stickers).toHaveLength(0)
|
|
})
|
|
|
|
it('closes the device picker when it emits update:modelValue=false', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
const picker = wrapper.findComponent({ name: 'DevicePicker' })
|
|
expect(picker.props('modelValue')).toBe(false)
|
|
await picker.vm.$emit('update:modelValue', true)
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.findComponent({ name: 'DevicePicker' }).props('modelValue')).toBe(true)
|
|
await picker.vm.$emit('update:modelValue', false)
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.findComponent({ name: 'DevicePicker' }).props('modelValue')).toBe(false)
|
|
})
|
|
|
|
it('confirm fired before any stickers/crop is a no-op (defensive guard)', async () => {
|
|
primeUploadStore()
|
|
const uploadSpy = vi.spyOn(useImagesStore(), 'uploadImage').mockResolvedValue({ id: 1 } as any)
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
// Picker exists but finalBlob is null — confirm should early-return
|
|
await wrapper.findComponent({ name: 'DevicePicker' }).vm.$emit('confirm')
|
|
await flushPromises()
|
|
expect(uploadSpy).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('reprocess on edit forwards undefined when cropParams/cropOrientation are null', async () => {
|
|
const upload = primeUploadStore()
|
|
upload.editingImageId = 33
|
|
upload.cropParams = null
|
|
upload.cropOrientation = null
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const reprocess = vi.spyOn(useImagesStore(), 'reprocessImage').mockResolvedValue({ id: 33 } as any)
|
|
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
// After crop, upload.cropParams is set by setCrop. Reset to null to exercise null branch.
|
|
upload.cropParams = null
|
|
upload.cropOrientation = null
|
|
await wrapper.findComponent({ name: 'StickerCanvas' }).vm.$emit('done', new Blob(['final']))
|
|
await flushPromises()
|
|
expect(reprocess).toHaveBeenCalledWith(33, expect.any(File), expect.objectContaining({
|
|
cropParams: undefined,
|
|
cropOrientation: undefined,
|
|
}))
|
|
})
|
|
|
|
it('uploadImage on a new upload forwards undefined for null optional fields', async () => {
|
|
const upload = primeUploadStore()
|
|
upload.originalFile = null
|
|
upload.cropParams = null
|
|
upload.cropOrientation = null
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const uploadSpy = vi.spyOn(useImagesStore(), 'uploadImage').mockResolvedValue({ id: 1 } as any)
|
|
|
|
// No originalFile → onMounted will redirect; instead set it after the redirect would have run
|
|
upload.originalFile = new File(['x'], 'x.jpg')
|
|
upload.originalUrl = 'blob:x'
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
// Reset to null after onCrop sets these via the store's setCrop
|
|
upload.cropParams = null
|
|
upload.cropOrientation = null
|
|
upload.originalFile = null
|
|
await wrapper.findComponent({ name: 'StickerCanvas' }).vm.$emit('done', new Blob(['final']))
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'DevicePicker' }).vm.$emit('confirm')
|
|
await flushPromises()
|
|
|
|
expect(uploadSpy).toHaveBeenCalledWith(expect.any(File), expect.objectContaining({
|
|
original: undefined,
|
|
cropParams: undefined,
|
|
cropOrientation: undefined,
|
|
}))
|
|
})
|
|
|
|
it('does not render the device picker when in edit mode', async () => {
|
|
const upload = primeUploadStore()
|
|
upload.editingImageId = 12
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
expect(wrapper.findComponent({ name: 'DevicePicker' }).exists()).toBe(false)
|
|
})
|
|
|
|
it('hides the back button on the done step', async () => {
|
|
primeUploadStore()
|
|
vi.spyOn(useImagesStore(), 'uploadImage').mockResolvedValue({ id: 1 } as any)
|
|
vi.spyOn(useImagesStore(), 'setApproval').mockResolvedValue()
|
|
vi.spyOn(useDevicesStore(), 'fetchDevices').mockResolvedValue()
|
|
|
|
const wrapper = mount(UploadView)
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'CropEditor' }).vm.$emit('crop', {
|
|
blob: new Blob(['x']),
|
|
params: { natX: 0, natY: 0, natW: 1, natH: 1 },
|
|
orientation: 'landscape',
|
|
})
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'StickerCanvas' }).vm.$emit('done', new Blob(['final']))
|
|
await flushPromises()
|
|
await wrapper.findComponent({ name: 'DevicePicker' }).vm.$emit('confirm')
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.upload-view__back').exists()).toBe(false)
|
|
expect(wrapper.text()).toContain('Added')
|
|
})
|
|
})
|