test: add payment flow tests and fix strict-mode e2e violations
Vitest:
- PaymentRedirect.spec.ts (8 tests): renders heading and 49 kr,
shows plate from query, Betalt button exists, calls payOrder on
click, navigates to /orders on success, shows error on failure,
disables button while paying, shows mock note
- ComposePage.spec.ts: update navigation test to expect /betalning
route with orderId param instead of /orders; add payment route
to test router; add PaymentRedirect import
Playwright E2E:
- payment-redirect.spec.ts (4 tests): compose→payment navigation,
Betalt→orders flow, auth guard redirects to login, mock note
visible
- compose.spec.ts: rename test and update assertion from /orders
to /betalning/ URL pattern; use getByRole('heading',
{ name: 'Betalning' }) to avoid strict mode violation with
mock-note paragraph containing the word 'Betalning'
This commit is contained in:
parent
c3c1513ac1
commit
8cd7991603
6 changed files with 208 additions and 13 deletions
|
|
@ -43,8 +43,8 @@ test.describe('Admin dashboard', () => {
|
|||
await page.goto('/admin')
|
||||
|
||||
await expect(page.locator('.admin-dashboard__plate').first()).toBeVisible()
|
||||
await expect(page.getByText('DEF456')).toBeVisible()
|
||||
await expect(page.getByText('GHI789')).toBeVisible()
|
||||
await expect(page.getByText('DEF456').first()).toBeVisible()
|
||||
await expect(page.getByText('GHI789').first()).toBeVisible()
|
||||
})
|
||||
|
||||
test('click row expands letter content', async ({ page }) => {
|
||||
|
|
@ -109,7 +109,7 @@ test.describe('Admin dashboard', () => {
|
|||
test('hides PostNord link when trackingId is null', async ({ page }) => {
|
||||
await page.goto('/admin')
|
||||
|
||||
const defRow = page.locator('.admin-dashboard__row', { hasText: 'DEF456' })
|
||||
const defRow = page.locator('.admin-dashboard__row', { hasText: 'DEF456' }).first()
|
||||
await defRow.click()
|
||||
|
||||
const trackingLink = page.locator('.admin-dashboard__tracking-link')
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ test.describe('Compose flow', () => {
|
|||
await expect(button).toBeDisabled()
|
||||
})
|
||||
|
||||
test('can create order and navigate to orders page', async ({ page }) => {
|
||||
test('can create order and navigate to payment page', async ({ page }) => {
|
||||
await page.goto('/logga-in')
|
||||
await page.getByLabel('E-postadress').fill('test@bilhalsning.se')
|
||||
await page.getByLabel('Lösenord').fill('test1234')
|
||||
|
|
@ -62,10 +62,9 @@ test.describe('Compose flow', () => {
|
|||
await expect(button).toBeEnabled()
|
||||
await button.click()
|
||||
|
||||
await expect(page).toHaveURL('/orders')
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Mina beställningar' }),
|
||||
).toBeVisible()
|
||||
await expect(page).toHaveURL(/\/betalning\//)
|
||||
await expect(page.getByRole('heading', { name: 'Betalning' })).toBeVisible()
|
||||
await expect(page.getByText('49 kr')).toBeVisible()
|
||||
})
|
||||
|
||||
test('preview shows letter content and GDPR footer', async ({ page }) => {
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ test.describe('Order history', () => {
|
|||
|
||||
await expect(page.getByRole('heading', { name: 'Mina beställningar' })).toBeVisible()
|
||||
await expect(page.getByText('ABC123').first()).toBeVisible()
|
||||
await expect(page.getByText('DEF456')).toBeVisible()
|
||||
await expect(page.getByText('GHI789')).toBeVisible()
|
||||
await expect(page.getByText('DEF456').first()).toBeVisible()
|
||||
await expect(page.getByText('GHI789').first()).toBeVisible()
|
||||
})
|
||||
|
||||
test('shows correct status badges', async ({ page }) => {
|
||||
|
|
@ -50,7 +50,7 @@ test.describe('Order history', () => {
|
|||
await page.goto('/orders')
|
||||
|
||||
await expect(page.getByText('Skickat')).toBeVisible()
|
||||
await expect(page.getByText('Väntar på betalning')).toBeVisible()
|
||||
await expect(page.getByText('Väntar på betalning').first()).toBeVisible()
|
||||
await expect(page.getByText('Levererat').first()).toBeVisible()
|
||||
})
|
||||
|
||||
|
|
|
|||
52
frontend/e2e/payment-redirect.spec.ts
Normal file
52
frontend/e2e/payment-redirect.spec.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test.describe('Payment redirect', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/logga-in')
|
||||
await page.getByLabel('E-postadress').fill('test@bilhalsning.se')
|
||||
await page.getByLabel('Lösenord').fill('test1234')
|
||||
await page.getByRole('button', { name: 'Logga in' }).click()
|
||||
await page.waitForURL('/')
|
||||
})
|
||||
|
||||
test('can navigate to payment page from compose', async ({ page }) => {
|
||||
await page.goto('/compose?plate=ABC123')
|
||||
await page.getByLabel('Ditt meddelande').fill('Hej fin bil!')
|
||||
await page.getByRole('button', { name: 'Skicka brev (49 kr)' }).click()
|
||||
|
||||
await expect(page).toHaveURL(/\/betalning\//)
|
||||
await expect(page.getByRole('heading', { name: 'Betalning' })).toBeVisible()
|
||||
await expect(page.getByText('49 kr')).toBeVisible()
|
||||
await expect(page.getByText('ABC123')).toBeVisible()
|
||||
})
|
||||
|
||||
test('Betalt button marks order as paid and redirects to orders', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/compose?plate=DEF456')
|
||||
await page.getByLabel('Ditt meddelande').fill('Vill köpa din bil.')
|
||||
await page.getByRole('button', { name: 'Skicka brev (49 kr)' }).click()
|
||||
|
||||
await page.waitForURL(/\/betalning\//)
|
||||
await page.getByRole('button', { name: 'Betalt' }).click()
|
||||
|
||||
await expect(page).toHaveURL('/orders')
|
||||
await expect(page.getByText('DEF456').first()).toBeVisible()
|
||||
})
|
||||
|
||||
test('payment page requires authentication', async ({ page }) => {
|
||||
await page.evaluate(() => localStorage.clear())
|
||||
await page.goto('/betalning/some-id')
|
||||
|
||||
await expect(page).toHaveURL(/\/logga-in/)
|
||||
})
|
||||
|
||||
test('shows mock payment note', async ({ page }) => {
|
||||
await page.goto('/compose?plate=GHI789')
|
||||
await page.getByLabel('Ditt meddelande').fill('Hej!')
|
||||
await page.getByRole('button', { name: 'Skicka brev (49 kr)' }).click()
|
||||
|
||||
await page.waitForURL(/\/betalning\//)
|
||||
await expect(page.getByText(/mock-betalning/i)).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
|
@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils'
|
|||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import ComposePage from '@/pages/ComposePage.vue'
|
||||
import PaymentRedirect from '@/pages/PaymentRedirect.vue'
|
||||
|
||||
vi.mock('@/api/orders', () => ({
|
||||
createOrder: vi.fn(),
|
||||
|
|
@ -31,6 +32,11 @@ function createTestRouter() {
|
|||
name: 'orders',
|
||||
component: { template: '<div>Orders</div>' },
|
||||
},
|
||||
{
|
||||
path: '/betalning/:orderId',
|
||||
name: 'payment',
|
||||
component: PaymentRedirect,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
|
@ -122,12 +128,13 @@ describe('ComposePage', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('navigates to /orders on success', async () => {
|
||||
it('navigates to payment on success', async () => {
|
||||
mockCreateOrder.mockResolvedValue({
|
||||
id: 'order-1',
|
||||
plate: 'ABC123',
|
||||
status: 'pending_payment',
|
||||
trackingId: null,
|
||||
amountPaid: null,
|
||||
createdAt: '2025-01-01T00:00:00Z',
|
||||
})
|
||||
|
||||
|
|
@ -138,7 +145,8 @@ describe('ComposePage', () => {
|
|||
await button.trigger('submit')
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(router.currentRoute.value.name).toBe('orders')
|
||||
expect(router.currentRoute.value.name).toBe('payment')
|
||||
expect(router.currentRoute.value.params.orderId).toBe('order-1')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
136
frontend/src/__tests__/PaymentRedirect.spec.ts
Normal file
136
frontend/src/__tests__/PaymentRedirect.spec.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import PaymentRedirect from '@/pages/PaymentRedirect.vue'
|
||||
import OrdersPage from '@/pages/OrdersPage.vue'
|
||||
|
||||
vi.mock('@/api/payment', () => ({
|
||||
payOrder: vi.fn(),
|
||||
}))
|
||||
|
||||
import { payOrder } from '@/api/payment'
|
||||
const mockPayOrder = vi.mocked(payOrder)
|
||||
|
||||
function createTestRouter() {
|
||||
return createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', name: 'home', component: { template: '<div>Home</div>' } },
|
||||
{
|
||||
path: '/betalning/:orderId',
|
||||
name: 'payment',
|
||||
component: PaymentRedirect,
|
||||
},
|
||||
{
|
||||
path: '/orders',
|
||||
name: 'orders',
|
||||
component: OrdersPage,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
async function mountPage(orderId = 'order-1', plate = 'ABC123') {
|
||||
const pinia = createPinia()
|
||||
setActivePinia(pinia)
|
||||
|
||||
const router = createTestRouter()
|
||||
await router.push({
|
||||
name: 'payment',
|
||||
params: { orderId },
|
||||
query: { plate },
|
||||
})
|
||||
await router.isReady()
|
||||
|
||||
const wrapper = mount(PaymentRedirect, {
|
||||
global: { plugins: [router, pinia] },
|
||||
})
|
||||
|
||||
return { wrapper, router }
|
||||
}
|
||||
|
||||
describe('PaymentRedirect', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('renders heading and amount', async () => {
|
||||
const { wrapper } = await mountPage()
|
||||
expect(wrapper.text()).toContain('Betalning')
|
||||
expect(wrapper.text()).toContain('49 kr')
|
||||
})
|
||||
|
||||
it('shows plate from query', async () => {
|
||||
const { wrapper } = await mountPage('order-1', 'ABC123')
|
||||
expect(wrapper.text()).toContain('ABC123')
|
||||
})
|
||||
|
||||
it('shows Betalt button', async () => {
|
||||
const { wrapper } = await mountPage()
|
||||
const button = wrapper.find('.payment__button')
|
||||
expect(button.exists()).toBe(true)
|
||||
expect(button.text()).toBe('Betalt')
|
||||
})
|
||||
|
||||
it('shows mock payment note', async () => {
|
||||
const { wrapper } = await mountPage()
|
||||
expect(wrapper.text()).toContain('mock-betalning')
|
||||
})
|
||||
|
||||
it('calls payOrder on button click', async () => {
|
||||
mockPayOrder.mockResolvedValue({
|
||||
id: 'order-1',
|
||||
plate: 'ABC123',
|
||||
status: 'paid',
|
||||
trackingId: null,
|
||||
amountPaid: 49.0,
|
||||
createdAt: '2025-01-01T00:00:00Z',
|
||||
})
|
||||
|
||||
const { wrapper } = await mountPage()
|
||||
await wrapper.find('.payment__button').trigger('click')
|
||||
|
||||
expect(mockPayOrder).toHaveBeenCalledWith('order-1')
|
||||
})
|
||||
|
||||
it('navigates to orders on success', async () => {
|
||||
mockPayOrder.mockResolvedValue({
|
||||
id: 'order-1',
|
||||
plate: 'ABC123',
|
||||
status: 'paid',
|
||||
trackingId: null,
|
||||
amountPaid: 49.0,
|
||||
createdAt: '2025-01-01T00:00:00Z',
|
||||
})
|
||||
|
||||
const { wrapper, router } = await mountPage()
|
||||
await wrapper.find('.payment__button').trigger('click')
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(router.currentRoute.value.name).toBe('orders')
|
||||
})
|
||||
})
|
||||
|
||||
it('shows error on payment failure', async () => {
|
||||
mockPayOrder.mockRejectedValue(new Error('Network error'))
|
||||
|
||||
const { wrapper } = await mountPage()
|
||||
await wrapper.find('.payment__button').trigger('click')
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.text()).toContain('Kunde inte genomföra betalningen')
|
||||
})
|
||||
})
|
||||
|
||||
it('disables button while paying', async () => {
|
||||
mockPayOrder.mockImplementation(() => new Promise(() => {}))
|
||||
|
||||
const { wrapper } = await mountPage()
|
||||
const button = wrapper.find('.payment__button')
|
||||
await button.trigger('click')
|
||||
|
||||
expect(button.attributes('disabled')).toBeDefined()
|
||||
expect(button.text()).toBe('Bearbetar...')
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue