diff --git a/ui/src/__tests__/system-configuration-source.test.js b/ui/src/__tests__/system-configuration-source.test.js new file mode 100644 index 0000000..602d0bd --- /dev/null +++ b/ui/src/__tests__/system-configuration-source.test.js @@ -0,0 +1,87 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { setActivePinia, createPinia } from 'pinia' +import { mount, flushPromises } from '@vue/test-utils' +import SystemConfigurationView from '../views/SystemConfigurationView.vue' + +// The Source column renders icon-only; the readable name + business +// explanation live in a v-tooltip (project convention: tooltips are +// v-tooltip, never native `title`). We drive rows through the mocked +// fetch and stub the table so it renders the `cell.source` slot per row, +// plus the tooltip default slot, so we can inspect what's rendered. +const tableStub = { + name: 'VibrantDataTable', + props: ['items'], + template: '
', +} +const vtooltipStub = { template: '' } +const viconStub = { template: '' } + +// One row per known source type + a classpath-style path + an unknown type. +const ROWS = [ + { name: 'a', source: 'systemEnvironment', overridden: false }, + { name: 'b', source: 'systemProperties:foo', overridden: false }, + { name: 'c', source: 'database', overridden: true }, + { name: 'd', source: 'applicationConfig', overridden: false }, + { name: 'e', source: 'jar:file:/x/classpath/app.cfg', overridden: false }, + { name: 'f', source: 'totally-unknown-xyz', overridden: false }, +] + +function mountView() { + return mount(SystemConfigurationView, { + global: { + stubs: { + LjPageHeader: true, LjSearch: true, LjButton: true, LjDialog: true, + LjAvailabilityField: true, LigojConfirmDialog: true, RowActionsCog: true, + VibrantDataTable: tableStub, + 'v-tooltip': vtooltipStub, 'v-icon': viconStub, 'v-form': true, 'v-textarea': true, + }, + }, + }) +} + +describe('SystemConfigurationView — source column', () => { + beforeEach(() => { + setActivePinia(createPinia()) + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: true, status: 200, + headers: { get: () => 'application/json' }, + json: async () => ROWS, + text: async () => '', + clone() { return this }, + }) + }) + + it('renders the source as an icon only (no text label)', async () => { + const w = mountView() + await flushPromises() + expect(w.findAll('.srccell').length).toBe(ROWS.length) + // The legacy text label is gone everywhere. + expect(w.find('.src-txt').exists()).toBe(false) + // Every source pill carries exactly its icon. + expect(w.findAll('.srcpill .vi').length).toBe(ROWS.length) + }) + + it('exposes a tooltip with the bold name and a business explanation', async () => { + const w = mountView() + await flushPromises() + const first = w.findAll('.srccell')[0] + expect(first.find('.srcpill .vtt').exists()).toBe(true) + expect(first.find('.src-tip-name').text()).toBe('System Environment') + expect(first.find('.src-tip-exp').exists()).toBe(true) + }) + + it('falls back to the raw source string for unknown types', async () => { + const w = mountView() + await flushPromises() + const unknown = w.findAll('.srccell').at(-1) + expect(unknown.find('.src-tip-exp').text()).toBe('totally-unknown-xyz') + }) + + it('converts the overridden indicator title into a v-tooltip', async () => { + const w = mountView() + await flushPromises() + const overriddenCell = w.findAll('.srccell')[2] // row 'c' (overridden) + expect(overriddenCell.find('.ovr .vtt').exists()).toBe(true) + expect(w.find('.ovr[title]').exists()).toBe(false) + }) +}) diff --git a/ui/src/i18n/en.js b/ui/src/i18n/en.js index 98cb783..5329f7d 100644 --- a/ui/src/i18n/en.js +++ b/ui/src/i18n/en.js @@ -152,6 +152,11 @@ export default { 'system.config.tipOverridden': 'Overridden', 'system.config.sourcePrefix': 'Source: {source}', 'system.config.sourceOverridden': '{base} — overridden', + 'system.config.source.systemEnvironment': 'Operating-system environment variable.', + 'system.config.source.systemProperties': 'Java system property (-D…).', + 'system.config.source.applicationConfig': 'Application configuration file.', + 'system.config.source.database': 'Stored in the Ligoj database (editable here).', + 'system.config.source.classpath': 'Bundled in the application classpath.', // System → Cache 'system.cache.title': 'Caches', diff --git a/ui/src/i18n/fr.js b/ui/src/i18n/fr.js index d21f0b2..b9d77ec 100644 --- a/ui/src/i18n/fr.js +++ b/ui/src/i18n/fr.js @@ -151,6 +151,11 @@ export default { 'system.config.tipOverridden': 'Surchargée', 'system.config.sourcePrefix': 'Source : {source}', 'system.config.sourceOverridden': '{base} — surchargée', + 'system.config.source.systemEnvironment': 'Variable d\'environnement du système d\'exploitation.', + 'system.config.source.systemProperties': 'Propriété système Java (-D…).', + 'system.config.source.applicationConfig': 'Fichier de configuration de l\'application.', + 'system.config.source.database': 'Stockée en base Ligoj (modifiable ici).', + 'system.config.source.classpath': 'Embarquée dans le classpath de l\'application.', // Système → Cache 'system.cache.title': 'Caches', diff --git a/ui/src/views/SystemConfigurationView.vue b/ui/src/views/SystemConfigurationView.vue index f9b79b5..f6d86c2 100644 --- a/ui/src/views/SystemConfigurationView.vue +++ b/ui/src/views/SystemConfigurationView.vue @@ -64,10 +64,19 @@ {{ item.value }}