import { defineComponent, h } from 'vue' import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ApiResponseError } from '@/shared/api/http' import OwnedBusinessPhotosEditor from './OwnedBusinessPhotosEditor.vue' import { ownedBusinessFixture } from './OwnedBusinessFixtures' import type { OwnedBusinessPhoto } from '../ownedBusinessTypes' const calls=vi.hoisted(()=>({upload:vi.fn(),bind:vi.fn(),command:vi.fn(),detail:vi.fn(),content:vi.fn(),actor:{user:{id:'actor'}},canUpload:true})) vi.mock('../api/ownedBusiness',()=>({uploadOwnedBusinessPhoto:calls.upload,bindOwnedBusinessPhoto:calls.bind,getOwnedBusinessCommand:calls.command,getOwnedBusiness:calls.detail,getOwnedBusinessPhotoContent:calls.content})) vi.mock('@/core/auth/auth',()=>({useAuthStore:()=>calls.actor})) vi.mock('@/core/access/accessPreview',()=>({useAccessPreviewStore:()=>({can:()=>calls.canUpload})})) vi.mock('@/core/unsaved-work/useUnsavedWorkRegistration',()=>({useUnsavedWorkRegistration:()=>({captureBaseline:vi.fn()})})) vi.mock('element-plus',()=>({ElMessage:{success:vi.fn()},ElMessageBox:{confirm:vi.fn()}})) const Frame=defineComponent({props:['modelValue','busy','beforeClose'],setup(_props,{slots}){return()=>h('div',Object.values(slots).map(slot=>slot?.()))}}) const Button=defineComponent({props:['disabled'],template:''}) const global={stubs:{OwnedBusinessPhotosDialog:Frame,ElButton:Button,LoadErrorState:{props:['message'],template:'
{{message}}
'},ApiErrorMeta:true}} const photo:OwnedBusinessPhoto={slot:'LOADING_SCENE',category:'LOADING',fileId:'9007199254740993',fileName:'loading.png',contentType:'image/png',sizeBytes:'5',uploadedAt:'2026-10-20T10:00:00Z',savedAt:null} async function choose(wrapper:ReturnType){const input=wrapper.find('input[type="file"]');Object.defineProperty(input.element,'files',{configurable:true,value:[new File(['image'],'loading.png',{type:'image/png'})]});await input.trigger('change');await flushPromises()} beforeEach(()=>{ vi.clearAllMocks();sessionStorage.clear();calls.actor.user.id='actor';calls.canUpload=true calls.upload.mockResolvedValue(photo);calls.command.mockRejectedValue(new ApiResponseError('not found','A0404',404)) const current=ownedBusinessFixture();current.row.recordVersion='1';current.photos=[{...photo,savedAt:'2026-10-20T10:01:00Z'}] calls.bind.mockResolvedValue(current);calls.detail.mockResolvedValue(current) }) describe('owned business per-slot photo save',()=>{ it('unlocks after binding failure and accepts a fresh photo submission',async()=>{ calls.bind.mockRejectedValueOnce(new TypeError('network interrupted')) const wrapper=mount(OwnedBusinessPhotosEditor,{global,props:{modelValue:true,detail:ownedBusinessFixture()}});await choose(wrapper) expect(calls.upload).toHaveBeenCalledTimes(1);expect(calls.bind).toHaveBeenCalledTimes(1) expect(wrapper.find('input[type="file"]').attributes('disabled')).toBeUndefined();expect(wrapper.emitted('saved')).toBeUndefined() const original={...calls.bind.mock.calls[0]![2]} await choose(wrapper) expect(calls.upload).toHaveBeenCalledTimes(2);expect(calls.bind).toHaveBeenCalledTimes(2) expect(calls.bind.mock.calls[1]![2].idempotencyKey).not.toBe(original.idempotencyKey);expect(original.fileId).toBe(photo.fileId);expect(original.recordVersion).toBe('1') expect(wrapper.emitted('saved')).toHaveLength(1);wrapper.unmount() }) it('allows another upload after an unknown failure without fabricating a saved photo',async()=>{ calls.upload.mockRejectedValue(new TypeError('network interrupted')) const wrapper=mount(OwnedBusinessPhotosEditor,{global,props:{modelValue:true,detail:ownedBusinessFixture()}});await choose(wrapper) await choose(wrapper) expect(calls.upload).toHaveBeenCalledTimes(2);expect(calls.bind).not.toHaveBeenCalled();expect(wrapper.emitted('saved')).toBeUndefined() expect(wrapper.find('input[type="file"]').attributes('disabled')).toBeUndefined();expect(sessionStorage.length).toBe(0);wrapper.unmount() }) it('keeps saved metadata readable without upload permission and fetches image bytes only on explicit preview',async()=>{ calls.canUpload=false const detail=ownedBusinessFixture();detail.photos=[{...photo,savedAt:'2026-10-20T10:01:00Z'}] const wrapper=mount(OwnedBusinessPhotosEditor,{global,props:{modelValue:true,detail}});await flushPromises() expect(wrapper.find('input[type="file"]').exists()).toBe(false);expect(wrapper.text()).toContain('已保存');expect(calls.content).not.toHaveBeenCalled() wrapper.unmount() }) })