You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
9.0 KiB

import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
import ElementUI from 'element-ui'
import FieldSettings from '@/views/survey/components/SurveyFieldSettings.vue'
import Editor from '@/views/survey/components/editPane.vue'
import Question from '@/views/survey/components/SurveyQuestion.vue'
import PreviewField from '@/views/survey/components/SurveyPreviewField.vue'
import Description from '@/views/survey/components/SurveyDescription.vue'
import Results from '@/views/survey/components/SurveyResultsDialog.vue'
import { save } from '@/api/survey/index'
import { statistics, show } from '@/api/survey/evaluationForm'
jest.mock('@/api/survey/index', () => ({
save: jest.fn(() => Promise.resolve({ id: 1 }))
}))
jest.mock('@/api/survey/evaluationForm', () => ({
statistics: jest.fn(),
index: jest.fn(),
show: jest.fn(),
textAnswers: jest.fn()
}))
const localVue = createLocalVue()
localVue.use(ElementUI)
const flush = () => new Promise((resolve) => setTimeout(resolve, 0))
describe('Survey editing and results', () => {
afterEach(() => {
jest.clearAllMocks()
})
it('saves mixed dimensions with zero lower bound and stable option keys', async() => {
const wrapper = shallowMount(Editor, {
localVue,
propsData: {
question: {
name: '资源对接',
field: 'resource',
edit_input: 'multi_dimension',
dimensions: [
{
name: '是否跟进',
field: 'follow',
select_item: [{ key: 'yes', value: '是' }],
need_fill: true
},
{
name: '意向数量',
field: 'count',
edit_input: 'number',
config: { integer: true, min: 0 },
need_fill: true
}
]
}
}
})
await wrapper.vm.save()
const payload = save.mock.calls[0][0]
expect(payload.dimensions[0].select_item[0].key).toBe('yes')
expect(payload.dimensions[1].config.min).toBe(0)
expect(payload.dimensions[1].edit_input).toBe('number')
expect(payload.dimensions.every((d) => !d.need_fill && d.rule === '')).toBe(
true
)
expect(wrapper.emitted().saved).toBeTruthy()
wrapper.destroy()
})
it('keeps a cleared numeric maximum unlimited instead of defaulting to zero', () => {
const field = {
edit_input: 'number',
config: { integer: true, min: 0, max: undefined }
}
const wrapper = mount(
{
render(h) {
return h('el-form', [h(FieldSettings, { props: { field }})])
}
},
{ localVue }
)
expect(field.config.max).toBeUndefined()
expect(wrapper.findAll('input[type="number"]').at(1).element.value).toBe(
''
)
wrapper.destroy()
})
it('renders zero as an answer and chapter headings without inputs', () => {
const wrapper = mount(Question, {
localVue,
propsData: {
answered: true,
question: { name: '数量', edit_input: 'number', display_value: '0' }
}
})
expect(wrapper.find('.answer').text()).toBe('0')
wrapper.destroy()
const section = mount(Question, {
localVue,
propsData: {
question: {
name: '课程评价',
edit_input: 'section',
help: '请选择评分'
}
}
})
expect(section.findAll('input').length).toBe(0)
expect(section.text()).toContain('请选择评分')
section.destroy()
})
it('shows score labels once and reveals selected supplementary input', async() => {
const wrapper = mount(Question, {
localVue,
propsData: {
question: {
name: '评分',
edit_input: 'radio',
config: { is_score: true, other_key: 'custom' },
select_item: [{ key: 'custom', value: '10分', score: 10 }]
}
}
})
expect(wrapper.text().match(/10分/g)).toHaveLength(1)
wrapper.setData({ previewValue: 'custom' })
await wrapper.vm.$nextTick()
expect(
wrapper.find('textarea[placeholder="请填写补充说明"]').exists()
).toBe(true)
wrapper.destroy()
})
it('shows all reply options with selected radio and checkbox values read-only', () => {
for (const type of ['radio', 'checkbox']) {
const wrapper = mount(Question, {
localVue,
propsData: {
answered: true,
question: {
name: '选择',
edit_input: type,
selected_keys: ['0'],
display_value: '零分',
select_item: [
{ key: 0, value: '零分' },
{ key: 'other', value: '其他' }
]
}
}
})
expect(wrapper.findAll('input')).toHaveLength(2)
expect(wrapper.findAll('input').at(0).element.checked).toBe(true)
expect(wrapper.findAll('input').at(1).element.checked).toBe(false)
expect(
wrapper
.findAll('input')
.wrappers.every((input) => input.element.disabled)
).toBe(true)
expect(wrapper.text()).toContain('学员选择:零分')
expect(wrapper.text()).toContain('其他')
wrapper.destroy()
}
const combo = mount(Question, {
localVue,
propsData: {
answered: true,
question: {
name: '组合',
edit_input: 'multi_dimension',
dimensions: [
{
field: 'intent',
name: '意向',
selected_keys: ['no'],
display_value: '否',
select_item: [
{ key: 'yes', value: '是' },
{ key: 'no', value: '否' }
]
}
]
}
}
})
expect(combo.findAll('input')).toHaveLength(2)
expect(combo.findAll('input').at(1).element.checked).toBe(true)
combo.destroy()
})
it('uses wrapping scores and vertical ordinary choices for preview fields', () => {
const score = mount(PreviewField, {
localVue,
propsData: {
field: {
edit_input: 'radio',
config: { is_score: true },
select_item: [{ key: '5', value: '5分' }]
}
}
})
expect(score.find('.scores').exists()).toBe(true)
score.destroy()
const ordinary = mount(PreviewField, {
localVue,
propsData: {
field: {
edit_input: 'radio',
config: {},
select_item: [{ key: 'yes', value: '是' }]
}
}
})
expect(ordinary.find('.scores').exists()).toBe(false)
ordinary.destroy()
const number = mount(PreviewField, {
localVue,
propsData: {
field: { edit_input: 'number', config: { integer: true, min: 0 }}
}
})
expect(number.find('input').attributes('placeholder')).toBe('请输入数量')
expect(number.text()).not.toContain('最小')
number.destroy()
})
it('renders rich descriptions safely and preserves plain text', () => {
const rich = mount(Description, {
propsData: {
value:
'<p style="text-indent:2em"><strong>说明</strong><img src="/a.png" onerror="alert(1)"><script>alert(1)</script></p>'
}
})
expect(rich.find('strong').text()).toBe('说明')
expect(rich.find('p').element.style.textIndent).toBe('2em')
expect(rich.find('script').exists()).toBe(false)
expect(rich.find('img').attributes('onerror')).toBeUndefined()
rich.destroy()
const plain = mount(Description, {
propsData: { value: '第一行\n第二行' }
})
expect(plain.text()).toContain('第一行\n第二行')
plain.destroy()
})
it('loads real statistics and the server reply snapshot', async() => {
statistics.mockResolvedValue({
responses: 2,
questions_count: 1,
average: 4,
questions: [
{
id: 1,
name: '满意度',
edit_input: 'radio',
stats: {
valid_count: 2,
average: 4,
other_count: 0,
options: [{ label: '满意', count: 2, percentage: 100 }]
}
}
]
})
show.mockResolvedValue({
id: 7,
snapshot: {
title: '提交时的标题',
desc: '<p><strong>历史问卷说明</strong></p>'
},
user: { name: '学员' },
questions: [
{ id: 1, name: '历史题目', edit_input: 'number', display_value: '0' }
]
})
const wrapper = mount(Results, {
localVue,
propsData: {
visible: false,
surveyData: { id: 10, title: '现在的标题' }
}
})
wrapper.setProps({ visible: true })
await flush()
expect(statistics).toHaveBeenCalledWith({
course_content_evaluation_id: 10
})
expect(wrapper.text()).toContain('有效作答 2 人')
await wrapper.vm.openReply({ id: 7 })
await flush()
expect(wrapper.vm.reply.snapshot.title).toBe('提交时的标题')
expect(document.body.querySelector('.reply .description strong').textContent).toBe('历史问卷说明')
expect(wrapper.text()).not.toContain('<strong>')
expect(wrapper.vm.reply.questions[0].display_value).toBe('0')
wrapper.destroy()
})
})