feat: consulta pública de pedidos por código
This commit is contained in:
47
tests/unit/components/OrderAccessInfo.spec.js
Normal file
47
tests/unit/components/OrderAccessInfo.spec.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import OrderAccessInfo from '@/components/order/OrderAccessInfo.vue'
|
||||
import vuetify from '@/plugins/vuetify'
|
||||
|
||||
function mountAccessInfo (code) {
|
||||
return mount(OrderAccessInfo, {
|
||||
props: { code },
|
||||
global: { plugins: [vuetify] },
|
||||
})
|
||||
}
|
||||
|
||||
describe('OrderAccessInfo', () => {
|
||||
beforeEach(() => {
|
||||
navigator.clipboard.writeText.mockClear()
|
||||
})
|
||||
|
||||
it('muestra el código del pedido', () => {
|
||||
const wrapper = mountAccessInfo('abc123')
|
||||
expect(wrapper.text()).toContain('abc123')
|
||||
})
|
||||
|
||||
it('muestra el link público construido con el código', () => {
|
||||
const wrapper = mountAccessInfo('abc123')
|
||||
expect(wrapper.text()).toContain(
|
||||
`${window.location.origin}/pedido/abc123`
|
||||
)
|
||||
})
|
||||
|
||||
it('copia el link al presionar el botón de copiar link', async () => {
|
||||
const wrapper = mountAccessInfo('abc123')
|
||||
|
||||
await wrapper.find('[data-test="copy-link"]').trigger('click')
|
||||
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
||||
`${window.location.origin}/pedido/abc123`
|
||||
)
|
||||
})
|
||||
|
||||
it('copia el código al presionar el botón de copiar código', async () => {
|
||||
const wrapper = mountAccessInfo('abc123')
|
||||
|
||||
await wrapper.find('[data-test="copy-code"]').trigger('click')
|
||||
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('abc123')
|
||||
})
|
||||
})
|
||||
88
tests/unit/components/PublicOrderSummary.spec.js
Normal file
88
tests/unit/components/PublicOrderSummary.spec.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import PublicOrderSummary from '@/components/PublicOrderSummary.vue'
|
||||
import OrderAccessInfo from '@/components/order/OrderAccessInfo.vue'
|
||||
import OrderCustomer from '@/components/order/OrderCustomer.vue'
|
||||
import OrderLines from '@/components/order/OrderLines.vue'
|
||||
import OrderTotal from '@/components/order/OrderTotal.vue'
|
||||
import OrderPayment from '@/components/order/OrderPayment.vue'
|
||||
import vuetify from '@/plugins/vuetify'
|
||||
|
||||
const saleData = {
|
||||
id: 5,
|
||||
code: 'abc123',
|
||||
date: '2024-09-02T00:00:00Z',
|
||||
customer: { id: 1, name: 'Camilo' },
|
||||
payment_method: 'CASH',
|
||||
lines: [
|
||||
{ product: { id: 10, name: 'Panela' }, quantity: 2, unit_price: 3000 },
|
||||
{ product: { id: 11, name: 'Arroz' }, quantity: 1, unit_price: 5000 },
|
||||
],
|
||||
type: 'sale',
|
||||
}
|
||||
|
||||
const catalogData = {
|
||||
id: 7,
|
||||
code: 'def456',
|
||||
date: '2024-09-02T00:00:00Z',
|
||||
customer: { id: 1, name: 'Camilo' },
|
||||
lines: [{ product: { id: 10, name: 'Panela' }, quantity: 2, unit_price: 3000 }],
|
||||
type: 'catalog',
|
||||
}
|
||||
|
||||
function mountSummary (props) {
|
||||
return mount(PublicOrderSummary, {
|
||||
props,
|
||||
global: { plugins: [vuetify] },
|
||||
})
|
||||
}
|
||||
|
||||
describe('PublicOrderSummary', () => {
|
||||
it('renderiza el resumen de una venta con sus partes', () => {
|
||||
const wrapper = mountSummary({ purchase: saleData })
|
||||
|
||||
expect(wrapper.findComponent(OrderAccessInfo).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(OrderCustomer).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(OrderLines).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(OrderTotal).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(OrderPayment).exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('Camilo')
|
||||
expect(wrapper.text()).toContain('Panela')
|
||||
expect(wrapper.text()).toContain('Arroz')
|
||||
expect(wrapper.text().replace(/\u00A0/g, ' ')).toContain('11.000')
|
||||
})
|
||||
|
||||
it('renderiza un pedido de catálogo sin bloque de pago', () => {
|
||||
const wrapper = mountSummary({ purchase: catalogData })
|
||||
|
||||
expect(wrapper.findComponent(OrderAccessInfo).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(OrderCustomer).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(OrderLines).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(OrderTotal).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(OrderPayment).exists()).toBe(false)
|
||||
expect(wrapper.text()).toContain('Camilo')
|
||||
expect(wrapper.text()).toContain('Panela')
|
||||
})
|
||||
|
||||
it('muestra el estado de carga', () => {
|
||||
const wrapper = mountSummary({ purchase: null, loading: true })
|
||||
|
||||
expect(wrapper.text()).toContain('Cargando')
|
||||
})
|
||||
|
||||
it('muestra el mensaje de error cuando lo recibe', () => {
|
||||
const wrapper = mountSummary({
|
||||
purchase: null,
|
||||
error: 'No se encontró un pedido con ese código.',
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('No se encontró un pedido con ese código.')
|
||||
})
|
||||
|
||||
it('no renderiza datos cuando no hay pedido, carga ni error', () => {
|
||||
const wrapper = mountSummary({ purchase: null })
|
||||
|
||||
expect(wrapper.text()).not.toContain('Camilo')
|
||||
expect(wrapper.findComponent(OrderLines).exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
92
tests/unit/pages/pedido.spec.js
Normal file
92
tests/unit/pages/pedido.spec.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { createMemoryHistory, createRouter } from 'vue-router'
|
||||
import PedidoPage from '@/pages/pedido/[code].vue'
|
||||
import PublicOrderSummary from '@/components/PublicOrderSummary.vue'
|
||||
import vuetify from '@/plugins/vuetify'
|
||||
|
||||
const saleData = {
|
||||
id: 1,
|
||||
code: 'abc123',
|
||||
date: '2024-09-02T00:00:00Z',
|
||||
customer: { id: 1, name: 'Camilo' },
|
||||
payment_method: 'CASH',
|
||||
lines: [
|
||||
{ product: { id: 10, name: 'Panela' }, quantity: 2, unit_price: 3000 },
|
||||
],
|
||||
type: 'sale',
|
||||
}
|
||||
|
||||
async function mountPage (api, path) {
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [{ path: '/pedido/:code?', component: PedidoPage }],
|
||||
})
|
||||
router.push(path)
|
||||
await router.isReady()
|
||||
const wrapper = mount(PedidoPage, {
|
||||
global: {
|
||||
plugins: [router, vuetify],
|
||||
provide: { api },
|
||||
},
|
||||
})
|
||||
return { router, wrapper }
|
||||
}
|
||||
|
||||
async function waitForRouteParam (router, param, value, timeout = 1000) {
|
||||
const start = Date.now()
|
||||
while (Date.now() - start < timeout) {
|
||||
if (router.currentRoute.value.params[param] === value) return
|
||||
await new Promise(resolve => setTimeout(resolve, 10))
|
||||
}
|
||||
throw new Error(`route param ${param} nunca llegó a ser ${value}`)
|
||||
}
|
||||
|
||||
describe('página pública /pedido', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('sin código no consulta la API y muestra el buscador', async () => {
|
||||
const api = { getPublicOrderSummary: vi.fn() }
|
||||
const { wrapper } = await mountPage(api, '/pedido')
|
||||
await flushPromises()
|
||||
|
||||
expect(api.getPublicOrderSummary).not.toHaveBeenCalled()
|
||||
expect(wrapper.text()).toContain('Código del pedido')
|
||||
})
|
||||
|
||||
it('con código por URL consulta y muestra el resumen', async () => {
|
||||
const api = { getPublicOrderSummary: vi.fn().mockResolvedValue(saleData) }
|
||||
const { wrapper } = await mountPage(api, '/pedido/abc123')
|
||||
await flushPromises()
|
||||
|
||||
expect(api.getPublicOrderSummary).toHaveBeenCalledWith('abc123')
|
||||
expect(wrapper.findComponent(PublicOrderSummary).props('purchase')).toEqual(saleData)
|
||||
expect(wrapper.text()).toContain('Camilo')
|
||||
})
|
||||
|
||||
it('muestra mensaje de no encontrado cuando el código no existe', async () => {
|
||||
const api = {
|
||||
getPublicOrderSummary: vi.fn().mockRejectedValue({ response: { status: 404 } }),
|
||||
}
|
||||
const { wrapper } = await mountPage(api, '/pedido/zzzzzz')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.findComponent(PublicOrderSummary).props('error')).toContain('No se encontró')
|
||||
})
|
||||
|
||||
it('al consultar un código navega a /pedido/<code> y vuelve a consultar', async () => {
|
||||
const api = { getPublicOrderSummary: vi.fn().mockResolvedValue(saleData) }
|
||||
const { router, wrapper } = await mountPage(api, '/pedido/abc123')
|
||||
await flushPromises()
|
||||
api.getPublicOrderSummary.mockClear()
|
||||
|
||||
await wrapper.find('input').setValue('newcode')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await waitForRouteParam(router, 'code', 'newcode')
|
||||
|
||||
expect(router.currentRoute.value.params.code).toBe('newcode')
|
||||
expect(api.getPublicOrderSummary).toHaveBeenCalledWith('newcode')
|
||||
})
|
||||
})
|
||||
34
tests/unit/services/django-api.spec.js
Normal file
34
tests/unit/services/django-api.spec.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import http from '@/services/http'
|
||||
import DjangoApi from '@/services/django-api'
|
||||
|
||||
vi.mock('@/services/http', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
patch: vi.fn(),
|
||||
put: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('DjangoApi.getPublicOrderSummary', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('VITE_DJANGO_BASE_URL', 'http://backend.test')
|
||||
http.get.mockReset()
|
||||
http.get.mockResolvedValue({ data: { code: 'abc123', type: 'sale' } })
|
||||
})
|
||||
|
||||
it('consulta el resumen público por código en la ruta resumen_publico', async () => {
|
||||
const api = new DjangoApi()
|
||||
|
||||
const result = await api.getPublicOrderSummary('abc123')
|
||||
|
||||
expect(http.get).toHaveBeenCalledTimes(1)
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'http://backend.test/don_confiao/api/resumen_publico/abc123'
|
||||
)
|
||||
expect(result).toEqual({ code: 'abc123', type: 'sale' })
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user