import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { enableAutoUnmount, flushPromises } from '@vue/test-utils' import SettingsFormView from '../../src/views/SettingsFormView.vue' import SettingsIndexView from '../../src/views/SettingsIndexView.vue' import { setSettings } from '../../src/state/useSettings' import { toasts } from '../../src/state/useToasts' import { settingsFixture } from '../fixtures/typed' import { API, mountApp, requestsTo, resetState, type Route } from '../helpers' const PAGE = `${API}/settings/search` const failure = (status: number, message: string, details: Record = {}) => ({ status, body: { error: { code: 'x', message, details } }, }) function routes(overrides: Record = {}): Record { return { [`GET ${API}/settings`]: { body: settingsFixture.list }, [`GET ${PAGE}/schema`]: { body: settingsFixture.schema }, [`GET ${PAGE}`]: { body: settingsFixture.values }, ...overrides, } } async function openPage(overrides: Record = {}) { setSettings(settingsFixture.list.data) return mountApp('/settings/search', routes(overrides)) } beforeEach(() => { resetState() }) afterEach(() => { document.body.innerHTML = '' }) enableAutoUnmount(afterEach) describe('settings index (D-21)', () => { it('says so when no settings page is permitted', async () => { const { wrapper } = await mountApp('/settings', routes()) expect(wrapper.findComponent(SettingsIndexView).exists()).toBe(true) expect(wrapper.find('h1').text()).toBe('Ustawienia') expect(wrapper.find('section > p').text()).toBe('Brak dostępnych ustawień.') }) it('links every page by encoded code with its label and description', async () => { setSettings([{ ...settingsFixture.list.data[0]!, code: 'a b', category: '', description: '' }]) const { wrapper } = await mountApp('/settings', routes()) const link = wrapper.find('[data-settings="a b"]') expect(link.attributes('href')).toBe('/settings/a%20b') expect(link.text()).toBe('Search') expect(wrapper.find('[data-settings-group] h2').exists()).toBe(false) }) }) describe('settings form (D-21)', () => { it('titles the page from the entry and links back to the index', async () => { const { wrapper } = await openPage() expect(wrapper.findComponent(SettingsFormView).exists()).toBe(true) expect(wrapper.find('h1').text()).toBe('Search') expect(wrapper.find('header p').text()).toBe('Search engine options') expect(wrapper.find('[data-action="back"]').attributes('href')).toBe('/settings') expect(wrapper.find('[data-action="back"]').attributes('aria-label')).toBe('Wróć do ustawień') }) it('falls back to the schema name, then the code, without an entry', async () => { const { wrapper } = await mountApp('/settings/search', routes()) expect(wrapper.find('h1').text()).toBe('Search') expect(wrapper.find('header p').exists()).toBe(false) }) it('announces a failed load and disables saving', async () => { const { wrapper } = await openPage({ [`GET ${PAGE}`]: failure(500, 'Boom') }) expect(wrapper.find('[role="alert"]').text()).toBe('Nie udało się wczytać formularza.') expect(wrapper.find('form').exists()).toBe(false) }) it('toasts the server message for a refused save and the generic one for a network failure', async () => { const { wrapper } = await openPage({ [`PUT ${PAGE}`]: failure(403, 'Not allowed') }) await wrapper.find('[data-action="save"]').trigger('click') await flushPromises() expect(toasts.value[0]).toMatchObject({ text: 'Not allowed', tone: 'danger' }) wrapper.unmount() const offline = await openPage({ [`PUT ${PAGE}`]: () => { throw new TypeError('offline') }, }) await offline.wrapper.find('[data-action="save"]').trigger('click') await flushPromises() expect(toasts.value[0]).toMatchObject({ text: 'Nie udało się zapisać zmian. Spróbuj ponownie.', tone: 'danger' }) expect(offline.wrapper.find('[data-action="save"]').attributes('disabled')).toBeUndefined() }) it('maps a 422 onto fields, lists unknown keys in the banner, focuses the field and clears it on change', async () => { const { wrapper, calls } = await openPage({ [`PUT ${PAGE}`]: failure(422, 'Invalid', { engine_host: ['Unreachable'], other: ['Global problem'] }), }) await wrapper.find('[data-action="save"]').trigger('click') await flushPromises() expect(requestsTo(calls, 'PUT', PAGE)).toHaveLength(1) const input = wrapper.find('#setting-engine_host') expect(input.attributes('aria-invalid')).toBe('true') expect(wrapper.find('[data-error-banner]').text()).toContain('Global problem') await input.setValue('ok.local') expect(wrapper.find('#setting-engine_host').attributes('aria-invalid')).toBeUndefined() }) })