You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
9.8 KiB
JavaScript
314 lines
9.8 KiB
JavaScript
import { describe, it, expect, vi } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
import { h } from 'vue'
|
|
import ConvocatoriasSection from '../../src/components/WebPageSections/ConvocatoriasSection.vue'
|
|
|
|
// Mock Ant Design Vue components
|
|
const mockAntComponents = {
|
|
'a-badge': {
|
|
template: '<span class="a-badge"><slot /></span>',
|
|
props: ['count']
|
|
},
|
|
'a-card': {
|
|
template: '<div class="a-card"><slot /></div>'
|
|
},
|
|
'a-tag': {
|
|
template: '<span class="a-tag" :class="color"><slot /></span>',
|
|
props: ['color']
|
|
},
|
|
'a-divider': {
|
|
template: '<hr class="a-divider" />'
|
|
},
|
|
'a-button': {
|
|
template: '<button class="a-button" @click="$emit(\'click\')"><slot /><slot name="icon" /></button>',
|
|
props: ['type', 'size', 'ghost'],
|
|
emits: ['click']
|
|
},
|
|
'a-image': {
|
|
template: '<img class="a-image" :src="src" :alt="alt" />',
|
|
props: ['src', 'alt', 'preview']
|
|
},
|
|
'a-empty': {
|
|
template: '<div class="a-empty"><slot /></div>',
|
|
props: ['description']
|
|
}
|
|
}
|
|
|
|
// Mock icons
|
|
const mockIcons = {
|
|
FileTextOutlined: { template: '<span class="icon file-text" />' },
|
|
DollarOutlined: { template: '<span class="icon dollar" />' },
|
|
TeamOutlined: { template: '<span class="icon team" />' },
|
|
CalendarOutlined: { template: '<span class="icon calendar" />' },
|
|
FormOutlined: { template: '<span class="icon form" />' }
|
|
}
|
|
|
|
const createWrapper = (props = {}) => {
|
|
return mount(ConvocatoriasSection, {
|
|
props,
|
|
global: {
|
|
stubs: {
|
|
...mockAntComponents,
|
|
...mockIcons
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
describe('ConvocatoriasSection', () => {
|
|
describe('when no procesos are provided', () => {
|
|
it('renders empty state message', () => {
|
|
const wrapper = createWrapper({ procesos: [] })
|
|
|
|
expect(wrapper.find('.empty-state').exists()).toBe(true)
|
|
expect(wrapper.text()).toContain('No hay convocatorias vigentes en este momento')
|
|
})
|
|
|
|
it('does not render main or secondary cards', () => {
|
|
const wrapper = createWrapper({ procesos: [] })
|
|
|
|
expect(wrapper.find('.main-convocatoria-card').exists()).toBe(false)
|
|
expect(wrapper.find('.secondary-list').exists()).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('when one proceso is provided', () => {
|
|
const singleProceso = [
|
|
{
|
|
id: 1,
|
|
titulo: 'Admisión Ordinaria 2026',
|
|
subtitulo: 'Proceso regular de admisión',
|
|
descripcion: 'Descripción del proceso de admisión',
|
|
estado: 'publicado',
|
|
fecha_inicio_inscripcion: '2026-02-01',
|
|
fecha_fin_inscripcion: '2026-02-28',
|
|
link_preinscripcion: 'https://example.com/preinscripcion'
|
|
}
|
|
]
|
|
|
|
it('renders main convocatoria card with correct title', () => {
|
|
const wrapper = createWrapper({ procesos: singleProceso })
|
|
|
|
expect(wrapper.find('.main-convocatoria-card').exists()).toBe(true)
|
|
expect(wrapper.find('h3').text()).toBe('Admisión Ordinaria 2026')
|
|
})
|
|
|
|
it('renders the "Principal" badge on main card', () => {
|
|
const wrapper = createWrapper({ procesos: singleProceso })
|
|
|
|
expect(wrapper.find('.card-badge').exists()).toBe(true)
|
|
expect(wrapper.find('.card-badge').text()).toBe('Principal')
|
|
})
|
|
|
|
it('renders action buttons for requisitos, pagos, vacantes, and cronograma', () => {
|
|
const wrapper = createWrapper({ procesos: singleProceso })
|
|
|
|
const actionButtons = wrapper.findAll('.action-btn')
|
|
expect(actionButtons.length).toBe(4)
|
|
|
|
const buttonTexts = actionButtons.map(btn => btn.text())
|
|
expect(buttonTexts).toContain('Requisitos')
|
|
expect(buttonTexts).toContain('Pagos')
|
|
expect(buttonTexts).toContain('Vacantes')
|
|
expect(buttonTexts).toContain('Cronograma')
|
|
})
|
|
|
|
it('does not render secondary cards list when only one proceso', () => {
|
|
const wrapper = createWrapper({ procesos: singleProceso })
|
|
|
|
expect(wrapper.find('.secondary-list').exists()).toBe(false)
|
|
})
|
|
|
|
it('displays the estado tag with correct label', () => {
|
|
const wrapper = createWrapper({ procesos: singleProceso })
|
|
|
|
const statusTag = wrapper.find('.status-tag')
|
|
expect(statusTag.exists()).toBe(true)
|
|
expect(statusTag.text()).toBe('Abierto') // 'publicado' maps to 'Abierto'
|
|
})
|
|
|
|
it('displays inscription dates when provided', () => {
|
|
const wrapper = createWrapper({ procesos: singleProceso })
|
|
|
|
expect(wrapper.text()).toContain('Inscripciones:')
|
|
})
|
|
})
|
|
|
|
describe('when multiple procesos are provided', () => {
|
|
const multipleProcesos = [
|
|
{
|
|
id: 1,
|
|
titulo: 'Proceso Principal',
|
|
descripcion: 'Descripción del proceso principal',
|
|
estado: 'publicado'
|
|
},
|
|
{
|
|
id: 2,
|
|
titulo: 'Proceso Secundario 1',
|
|
descripcion: 'Descripción secundario 1',
|
|
estado: 'en_proceso'
|
|
},
|
|
{
|
|
id: 3,
|
|
titulo: 'Proceso Secundario 2',
|
|
descripcion: 'Descripción secundario 2',
|
|
estado: 'nuevo'
|
|
}
|
|
]
|
|
|
|
it('renders main card for first proceso', () => {
|
|
const wrapper = createWrapper({ procesos: multipleProcesos })
|
|
|
|
expect(wrapper.find('.main-convocatoria-card').exists()).toBe(true)
|
|
expect(wrapper.find('h3').text()).toBe('Proceso Principal')
|
|
})
|
|
|
|
it('renders secondary cards for remaining procesos', () => {
|
|
const wrapper = createWrapper({ procesos: multipleProcesos })
|
|
|
|
expect(wrapper.find('.secondary-list').exists()).toBe(true)
|
|
|
|
const secondaryCards = wrapper.findAll('.secondary-convocatoria-card')
|
|
expect(secondaryCards.length).toBe(2)
|
|
})
|
|
|
|
it('renders correct titles in secondary cards', () => {
|
|
const wrapper = createWrapper({ procesos: multipleProcesos })
|
|
|
|
const secondaryTitles = wrapper.findAll('.secondary-title')
|
|
expect(secondaryTitles[0].text()).toBe('Proceso Secundario 1')
|
|
expect(secondaryTitles[1].text()).toBe('Proceso Secundario 2')
|
|
})
|
|
|
|
it('renders estado tags with correct colors for different estados', () => {
|
|
const wrapper = createWrapper({ procesos: multipleProcesos })
|
|
|
|
const statusTags = wrapper.findAll('.status-tag')
|
|
// First is main card (publicado), then secondary cards
|
|
expect(statusTags[0].text()).toBe('Abierto') // publicado
|
|
expect(statusTags[1].text()).toBe('En Proceso') // en_proceso
|
|
expect(statusTags[2].text()).toBe('PRÓXIMAMENTE') // nuevo
|
|
})
|
|
})
|
|
|
|
describe('emits events correctly', () => {
|
|
const procesos = [
|
|
{
|
|
id: 1,
|
|
titulo: 'Proceso Test',
|
|
estado: 'publicado'
|
|
},
|
|
{
|
|
id: 2,
|
|
titulo: 'Proceso Secundario',
|
|
estado: 'publicado'
|
|
}
|
|
]
|
|
|
|
it('emits show-modal event with correct payload when action button is clicked', async () => {
|
|
const wrapper = createWrapper({ procesos })
|
|
|
|
const actionButtons = wrapper.findAll('.action-btn')
|
|
await actionButtons[0].trigger('click') // Requisitos button
|
|
|
|
expect(wrapper.emitted('show-modal')).toBeTruthy()
|
|
expect(wrapper.emitted('show-modal')[0]).toEqual([
|
|
{ procesoId: 1, tipo: 'requisitos' }
|
|
])
|
|
})
|
|
|
|
it('emits show-modal for pagos button', async () => {
|
|
const wrapper = createWrapper({ procesos })
|
|
|
|
const actionButtons = wrapper.findAll('.action-btn')
|
|
await actionButtons[1].trigger('click') // Pagos button
|
|
|
|
expect(wrapper.emitted('show-modal')[0]).toEqual([
|
|
{ procesoId: 1, tipo: 'pagos' }
|
|
])
|
|
})
|
|
|
|
it('emits show-modal for vacantes button', async () => {
|
|
const wrapper = createWrapper({ procesos })
|
|
|
|
const actionButtons = wrapper.findAll('.action-btn')
|
|
await actionButtons[2].trigger('click') // Vacantes button
|
|
|
|
expect(wrapper.emitted('show-modal')[0]).toEqual([
|
|
{ procesoId: 1, tipo: 'vacantes' }
|
|
])
|
|
})
|
|
|
|
it('emits show-modal for cronograma button', async () => {
|
|
const wrapper = createWrapper({ procesos })
|
|
|
|
const actionButtons = wrapper.findAll('.action-btn')
|
|
await actionButtons[3].trigger('click') // Cronograma button
|
|
|
|
expect(wrapper.emitted('show-modal')[0]).toEqual([
|
|
{ procesoId: 1, tipo: 'cronograma' }
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('estado mapping', () => {
|
|
const testEstadoCases = [
|
|
{ estado: 'publicado', expectedLabel: 'Abierto' },
|
|
{ estado: 'en_proceso', expectedLabel: 'En Proceso' },
|
|
{ estado: 'nuevo', expectedLabel: 'PRÓXIMAMENTE' },
|
|
{ estado: 'finalizado', expectedLabel: 'FINALIZADO' },
|
|
{ estado: 'cancelado', expectedLabel: 'CANCELADO' }
|
|
]
|
|
|
|
testEstadoCases.forEach(({ estado, expectedLabel }) => {
|
|
it(`maps estado "${estado}" to label "${expectedLabel}"`, () => {
|
|
const wrapper = createWrapper({
|
|
procesos: [{ id: 1, titulo: 'Test', estado }]
|
|
})
|
|
|
|
expect(wrapper.find('.status-tag').text()).toBe(expectedLabel)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('description display', () => {
|
|
it('displays descripcion when provided', () => {
|
|
const wrapper = createWrapper({
|
|
procesos: [{
|
|
id: 1,
|
|
titulo: 'Test',
|
|
descripcion: 'Esta es la descripción del proceso',
|
|
estado: 'publicado'
|
|
}]
|
|
})
|
|
|
|
expect(wrapper.find('.convocatoria-desc').text()).toBe('Esta es la descripción del proceso')
|
|
})
|
|
|
|
it('falls back to subtitulo when descripcion is not provided', () => {
|
|
const wrapper = createWrapper({
|
|
procesos: [{
|
|
id: 1,
|
|
titulo: 'Test',
|
|
subtitulo: 'Este es el subtítulo',
|
|
estado: 'publicado'
|
|
}]
|
|
})
|
|
|
|
expect(wrapper.find('.convocatoria-desc').text()).toBe('Este es el subtítulo')
|
|
})
|
|
|
|
it('shows default text when neither descripcion nor subtitulo is provided', () => {
|
|
const wrapper = createWrapper({
|
|
procesos: [{
|
|
id: 1,
|
|
titulo: 'Test',
|
|
estado: 'publicado'
|
|
}]
|
|
})
|
|
|
|
expect(wrapper.find('.convocatoria-desc').text()).toBe('Proceso de admisión')
|
|
})
|
|
})
|
|
})
|