import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import SkillProposal from '@/components/ui/SkillProposal.vue'
const PROPOSAL = {
id: 'pr1',
name: 'Выключение инфраструктуры',
description: 'Останавливает сервисы по порядку.',
area: 'infrastructure',
tags: ['power'],
status: 'pending',
replaces_id: null,
preconditions: ['Все предупреждены'],
steps: ['Остановить приложения', 'Остановить базы'],
post_checks: ['Юнитов не осталось'],
on_deviation: 'Остановиться и доложить.',
on_blocked: 'Прервать и доложить.',
on_already_done: 'Проверить и пропустить.',
body: 'Порядок зависимостей.',
}
vi.mock('@/api/index.js', () => ({
getSkillProposal: vi.fn(),
}))
import * as api from '@/api/index.js'
function mountWith(data, sendFn = vi.fn()) {
return mount(SkillProposal, {
props: { data },
global: { provide: { wsSend: sendFn } },
})
}
describe('SkillProposal', () => {
beforeEach(() => {
vi.clearAllMocks()
api.getSkillProposal.mockResolvedValue({ ...PROPOSAL })
})
it('renders the stored document', async () => {
const wrapper = mountWith({ proposal_id: 'pr1' })
await flushPromises()
expect(api.getSkillProposal).toHaveBeenCalledWith('pr1')
expect(wrapper.text()).toContain('Выключение инфраструктуры')
expect(wrapper.text()).toContain('Остановить приложения')
expect(wrapper.text()).toContain('Юнитов не осталось')
expect(wrapper.text()).toContain('Остановиться и доложить.')
expect(wrapper.text()).toContain('Порядок зависимостей.')
expect(wrapper.text()).toContain('New protocol')
})
it('asks the server to accept, and does not write anything itself', async () => {
const sendFn = vi.fn()
const wrapper = mountWith({ proposal_id: 'pr1' }, sendFn)
await flushPromises()
await wrapper.find('.skill-proposal-btn.is-accept').trigger('click')
expect(sendFn).toHaveBeenCalledWith({
type: 'skill_proposal',
proposal_id: 'pr1',
action: 'accept',
})
})
it('sends reject and leaves the document readable', async () => {
const sendFn = vi.fn()
const wrapper = mountWith({ proposal_id: 'pr1' }, sendFn)
await flushPromises()
await wrapper.find('.skill-proposal-btn.is-reject').trigger('click')
expect(sendFn).toHaveBeenCalledWith({
type: 'skill_proposal',
proposal_id: 'pr1',
action: 'reject',
})
expect(wrapper.text()).toContain('Остановить приложения')
})
it('turns into a decided card when the server answers', async () => {
const wrapper = mountWith({ proposal_id: 'pr1' })
await flushPromises()
await wrapper.setProps({ data: { proposal_id: 'pr1', status: 'accepted' } })
expect(wrapper.find('.skill-proposal-btn').exists()).toBe(false)
expect(wrapper.text()).toContain('Added to the catalog')
})
it('shows the stored decision after a reload', async () => {
api.getSkillProposal.mockResolvedValue({ ...PROPOSAL, status: 'rejected' })
const wrapper = mountWith({ proposal_id: 'pr1' })
await flushPromises()
expect(wrapper.text()).toContain('Rejected')
expect(wrapper.find('.skill-proposal-btn').exists()).toBe(false)
})
it('keeps the buttons and shows why a refused accept failed', async () => {
const wrapper = mountWith({ proposal_id: 'pr1' })
await flushPromises()
await wrapper.setProps({
data: { proposal_id: 'pr1', status: 'pending', error: 'already exists' },
})
expect(wrapper.text()).toContain('already exists')
expect(wrapper.find('.skill-proposal-btn.is-accept').exists()).toBe(true)
})
it('reports a proposal that cannot be loaded', async () => {
api.getSkillProposal.mockRejectedValue(new Error('404'))
const wrapper = mountWith({ proposal_id: 'gone' })
await flushPromises()
expect(wrapper.text()).toContain('could not be loaded')
})
})