From 72a7d494ca90d599de45a1a07ea4416e0986376c Mon Sep 17 00:00:00 2001 From: Norman Niati Date: Mon, 15 Jun 2026 15:21:15 +0200 Subject: [PATCH] feat(dashboard): persist subscriptions display mode per context Remember the cards/list choice of the shared subscriptions panel, independently for the dashboard and the project detail, in localStorage (key prefix ligoj-subview:). Defaults stay cards on the dashboard and list on the project detail. Cards mode now uses a 3-column grid, responsive down to 2 then 1 column on narrow widths. --- ui/src/__tests__/subscriptions-panel.test.js | 69 ++++++++++++++++++++ ui/src/components/SubscriptionsPanel.vue | 24 ++++++- ui/src/views/HomeView.vue | 2 +- ui/src/views/ProjectDetailView.vue | 2 +- 4 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 ui/src/__tests__/subscriptions-panel.test.js diff --git a/ui/src/__tests__/subscriptions-panel.test.js b/ui/src/__tests__/subscriptions-panel.test.js new file mode 100644 index 0000000..f029f65 --- /dev/null +++ b/ui/src/__tests__/subscriptions-panel.test.js @@ -0,0 +1,69 @@ +import { describe, it, expect, beforeEach } from 'vitest' +import { setActivePinia, createPinia } from 'pinia' +import { mount } from '@vue/test-utils' +import { LjSegmented, VibrantDataTable } from '@ligoj/host' +import SubscriptionsPanel from '../components/SubscriptionsPanel.vue' + +// Minimal grouped model: one tool with one subscription row. Enough for the +// component to render either the cards grid (`.sp-grid`) or the list table. +const GROUPS = [{ + key: 'tool-1', name: 'Tool 1', kind: 'kind', color: '#123456', icon: 'div', health: 'ok', + rows: [{ name: 'sub-a', status: 'ok', pills: [], sub: { id: 1 } }], +}] + +function mountPanel(props = {}) { + return mount(SubscriptionsPanel, { + props: { groups: GROUPS, ...props }, + global: { + // Stub the heavy host children; we only drive the LjSegmented v-model. + stubs: { LjSearch: true, VibrantDataTable: true, SubscriptionGroupCard: true, PluginFeatures: true }, + }, + }) +} + +// view === 'cards' renders the `.sp-grid` div; view === 'list' renders the table. +const isCards = (w) => w.find('.sp-grid').exists() + +describe('SubscriptionsPanel — view persistence', () => { + beforeEach(() => { setActivePinia(createPinia()) }) // setup.js clears the localStorage mock + + it('persists the selected view under ligoj-subview:', async () => { + const w = mountPanel({ storageKey: 'test', defaultView: 'list' }) + expect(isCards(w)).toBe(false) // starts on the default (list) + await w.findComponent(LjSegmented).vm.$emit('update:modelValue', 'cards') + expect(localStorage.getItem('ligoj-subview:test')).toBe('cards') + expect(isCards(w)).toBe(true) + }) + + it('restores the stored view on remount, overriding default-view', () => { + localStorage.setItem('ligoj-subview:test', 'cards') + const w = mountPanel({ storageKey: 'test', defaultView: 'list' }) + expect(isCards(w)).toBe(true) // restored to cards even though default is list + }) + + it('keeps contexts independent (home vs project)', async () => { + const home = mountPanel({ storageKey: 'home', defaultView: 'cards' }) + await home.findComponent(LjSegmented).vm.$emit('update:modelValue', 'list') + expect(localStorage.getItem('ligoj-subview:home')).toBe('list') + expect(localStorage.getItem('ligoj-subview:project')).toBe(null) + + const project = mountPanel({ storageKey: 'project', defaultView: 'list' }) + await project.findComponent(LjSegmented).vm.$emit('update:modelValue', 'cards') + expect(localStorage.getItem('ligoj-subview:project')).toBe('cards') + expect(localStorage.getItem('ligoj-subview:home')).toBe('list') // untouched + }) + + it('without storageKey, leaves localStorage untouched (backward compatible)', async () => { + const w = mountPanel({ defaultView: 'list' }) + await w.findComponent(LjSegmented).vm.$emit('update:modelValue', 'cards') + expect(localStorage.length).toBe(0) + expect(isCards(w)).toBe(true) // still reactive in-memory + }) + + it('ignores an invalid stored value and falls back to default-view', () => { + localStorage.setItem('ligoj-subview:test', 'garbage') + const w = mountPanel({ storageKey: 'test', defaultView: 'list' }) + expect(isCards(w)).toBe(false) + expect(w.findComponent(VibrantDataTable).exists()).toBe(true) + }) +}) diff --git a/ui/src/components/SubscriptionsPanel.vue b/ui/src/components/SubscriptionsPanel.vue index 87c93be..974f323 100644 --- a/ui/src/components/SubscriptionsPanel.vue +++ b/ui/src/components/SubscriptionsPanel.vue @@ -69,7 +69,7 @@