import Wizard from '@/views/survey/components/SurveyCreateWizard.vue' import SurveyList from '@/views/survey/index.vue' import { surveyCopyDraft } from '@/utils/surveyCopy' import { show, save } from '@/api/survey/evaluations' import { index as questionIndex } from '@/api/survey/index' jest.mock('@/api/survey/evaluations', () => ({ show: jest.fn(), save: jest.fn(), index: jest.fn(), destroy: jest.fn(), qrCode: jest.fn() })) jest.mock('@/api/survey/index', () => ({ index: jest.fn(), save: jest.fn(), destroy: jest.fn(), reorder: jest.fn() })) const source = { id: 10, title: '培训问卷', desc: '

说明

', course_id: '11', type_id: '3', audience_course_ids: [11, 12], start_time: '2026-12-01 00:00:00', end_time: '2026-12-31 23:59:59', status: 1, course_content_evaluation_forms_count: 20, qrCodeData: 'source-qr' } const questions = [ { id: 20, course_content_evaluation_id: 10, field: 'attend', name: '是否参加', edit_input: 'radio', sort: 1, select_item: [{ key: 'yes', value: '是' }] }, { id: 21, field: 'group', name: '组合题', edit_input: 'multi_dimension', sort: 2, dimensions: [{ field: 'count', edit_input: 'number', config: { min: 0 } }], config: { visible_when: { field: 'attend', values: ['yes'], required_values: ['yes'] } } } ] function wizard() { return { ...Wizard.data(), ...Wizard.methods, base: { requestToForm: (data, form) => Object.fromEntries(Object.keys(form).map(key => [key, data[key] === undefined ? form[key] : data[key]])) }, compareTime: () => false, $emit: jest.fn(), $message: jest.fn(), $refs: { dialog: { reset: jest.fn() } }, $nextTick: callback => callback() } } describe('Copy a questionnaire into the new-survey dialog', () => { beforeEach(() => jest.resetAllMocks()) it('opens an independent draft with all editable basics and deep-copied question settings, without saving', async() => { show.mockResolvedValue(source) questionIndex.mockResolvedValue({ data: questions }) const state = wizard() await state.openCopy(10, [{ id: 11, is_arrange: 0 }]) expect(state.isShow).toBe(true) expect(state.type).toBe('add') expect(state.id).toBe('') expect(state.form.id).toBeUndefined() expect(state.form.title).toBe('培训问卷(副本)') expect(state.form.status).toBe(0) expect(state.form.desc).toBe(source.desc) expect(state.form.audience_course_ids).toEqual([11, 12]) expect(state.form.start_time).toBe(source.start_time) expect(state.form.qrCodeData).toBeUndefined() expect(state.form.course_content_evaluation_forms_count).toBeUndefined() expect(state.questionList[0].id).not.toBe(20) state.questionList[1].dimensions[0].config.min = 5 state.questionList[1].config.visible_when.values.push('no') expect(questions[1].dimensions[0].config.min).toBe(0) expect(questions[1].config.visible_when.values).toEqual(['yes']) expect(save).not.toHaveBeenCalled() Wizard.watch.isShow.call(state, false) expect(state.copyFromId).toBe('') expect(state.questionList).toEqual([]) }) it('creates the copy only on save, reloads new question IDs and updates that same new survey on subsequent saves', async() => { show.mockResolvedValue(source) questionIndex.mockResolvedValueOnce({ data: questions }).mockResolvedValueOnce({ data: questions.map(question => ({ ...question, id: question.id + 100, course_content_evaluation_id: 100 })) }) const state = wizard() await state.openCopy(10, [{ id: 11, is_arrange: 0 }]) save.mockResolvedValue({ ...source, id: 100, status: 0 }) await state.submit() expect(save.mock.calls[0][0].copy_from_id).toBe(10) expect(save.mock.calls[0][0].id).toBeUndefined() expect(state.id).toBe(100) expect(state.questionLinkIds.course_content_evaluation_id).toBe(100) expect(state.questionList[0].id).toBe(120) state.form.title = '修改新问卷标题' await state.submit() expect(save.mock.calls[1][0].id).toBe(100) expect(save.mock.calls[1][0].copy_from_id).toBeUndefined() expect(questionIndex).toHaveBeenCalledTimes(2) }) it('restores selected schedules without retaining the original questionnaire link', async() => { show.mockResolvedValue({ ...source, course_content_id: '8,9' }) questionIndex.mockResolvedValue({ data: questions }) const state = wizard() state.filterSurveys = async() => { state.courseContentList = [{ id: 8 }, { id: 9 }, { id: 10 }] } await state.openCopy(10, [{ id: 11, is_arrange: 1 }]) expect(state.courseContentList.map(item => !!item.checked)).toEqual([true, true, false]) expect(state.selectedCourseSchedules.map(item => item.id)).toEqual([8, 9]) expect(state.questionLinkIds.course_content_evaluation_id).toBe('') }) it('does not open or create a copy when loading fails and unlocks the list copy menu for retry', async() => { const state = wizard() show.mockRejectedValue(new Error('missing source')) questionIndex.mockResolvedValue({ data: [] }) await expect(state.openCopy(10, [])).rejects.toThrow('missing source') expect(state.isShow).toBe(false) expect(save).not.toHaveBeenCalled() const list = { copyingSurveyId: null, courseList: [], $refs: { SurveyCreateWizard: state }, $message: { error: jest.fn() } } await SurveyList.methods.duplicateSurvey.call(list, 10) expect(list.copyingSurveyId).toBeNull() expect(list.$message.error).toHaveBeenCalled() }) it('preserves selected schedule links and keeps the draft name within the existing title limit', () => { const draft = surveyCopyDraft({ ...source, title: '题'.repeat(255), course_content_id: '8,9' }, questions) expect(draft.form.course_content_id).toBe('8,9') expect(draft.form.title.length).toBeLessThanOrEqual(255) }) })