Merge pull request #16 from josedario87/feature/default-module-view-setting
Feature/default module view setting
This commit is contained in:
@@ -48,10 +48,16 @@ describe('useUi Store', () => {
|
||||
expect(store.accentColorTareas).toBe('#4CAF50')
|
||||
expect(store.accentColorPlanillas).toBe('#FF9800')
|
||||
expect(store.accentColorAsistencias).toBe('#E91E63')
|
||||
expect(localStorageMock.getItem).toHaveBeenCalledWith(APPEARANCE_STORAGE_KEY)
|
||||
})
|
||||
|
||||
it('loads settings from localStorage including new accent colors if present', () => {
|
||||
expect(store.accentColorConfiguracion).toBe('#607D8B')
|
||||
// Check new default view defaults
|
||||
expect(store.defaultViewEmpleados).toBe('table')
|
||||
expect(store.defaultViewTareas).toBe('table')
|
||||
expect(store.defaultViewPlanillas).toBe('table')
|
||||
expect(store.defaultViewAsistencias).toBe('table')
|
||||
expect(store.defaultViewConfiguracion).toBe('table')
|
||||
// Check other new defaults
|
||||
expect(store.tableBgColorEmpleados).toBe('#FFFFFF')
|
||||
expect(store.desktopNavbarPersistent).toBe(false)
|
||||
expect(localStorageMock.getItem).toHaveBeenCalledWith(APPEARANCE_STORAGE_KEY)
|
||||
})
|
||||
|
||||
@@ -65,7 +71,18 @@ describe('useUi Store', () => {
|
||||
accentColorTareas: '#00FF00',
|
||||
accentColorPlanillas: '#FFFF00',
|
||||
accentColorAsistencias: '#FF00FF',
|
||||
// other settings...
|
||||
accentColorConfiguracion: '#112233',
|
||||
tableBgColorEmpleados: '#EEEEEE',
|
||||
tableBgColorTareas: '#DDDDDD',
|
||||
tableBgColorPlanillas: '#CCCCCC',
|
||||
tableBgColorAsistencias: '#BBBBBB',
|
||||
tableBgColorConfiguracion: '#AAAAAA',
|
||||
desktopNavbarPersistent: true,
|
||||
defaultViewEmpleados: 'card',
|
||||
defaultViewTareas: 'card',
|
||||
defaultViewPlanillas: 'card',
|
||||
defaultViewAsistencias: 'card',
|
||||
defaultViewConfiguracion: 'card',
|
||||
}
|
||||
localStorageMock.getItem.mockReturnValueOnce(JSON.stringify(storedSettings))
|
||||
|
||||
@@ -80,9 +97,21 @@ describe('useUi Store', () => {
|
||||
expect(store.accentColorTareas).toBe('#00FF00')
|
||||
expect(store.accentColorPlanillas).toBe('#FFFF00')
|
||||
expect(store.accentColorAsistencias).toBe('#FF00FF')
|
||||
expect(store.accentColorConfiguracion).toBe('#112233')
|
||||
expect(store.tableBgColorEmpleados).toBe('#EEEEEE')
|
||||
expect(store.tableBgColorTareas).toBe('#DDDDDD')
|
||||
expect(store.tableBgColorPlanillas).toBe('#CCCCCC')
|
||||
expect(store.tableBgColorAsistencias).toBe('#BBBBBB')
|
||||
expect(store.tableBgColorConfiguracion).toBe('#AAAAAA')
|
||||
expect(store.desktopNavbarPersistent).toBe(true)
|
||||
expect(store.defaultViewEmpleados).toBe('card')
|
||||
expect(store.defaultViewTareas).toBe('card')
|
||||
expect(store.defaultViewPlanillas).toBe('card')
|
||||
expect(store.defaultViewAsistencias).toBe('card')
|
||||
expect(store.defaultViewConfiguracion).toBe('card')
|
||||
})
|
||||
|
||||
it('loads older settings from localStorage and uses defaults for new accent colors if not present', () => {
|
||||
it('loads older settings from localStorage and uses defaults for new settings if not present', () => {
|
||||
const olderStoredSettings = {
|
||||
primaryColor: '#ABCDEF',
|
||||
theme: 'dark',
|
||||
@@ -96,10 +125,20 @@ describe('useUi Store', () => {
|
||||
expect(store.theme).toBe('dark')
|
||||
expect(store.fontSize).toBe(18)
|
||||
// New accent colors should fall back to defaults
|
||||
expect(store.accentColorEmpleados).toBe('#2196F3')
|
||||
expect(store.accentColorTareas).toBe('#4CAF50')
|
||||
expect(store.accentColorPlanillas).toBe('#FF9800')
|
||||
expect(store.accentColorAsistencias).toBe('#E91E63')
|
||||
expect(store.accentColorEmpleados).toBe('#2196F3') // Default
|
||||
expect(store.accentColorTareas).toBe('#4CAF50') // Default
|
||||
expect(store.accentColorPlanillas).toBe('#FF9800') // Default
|
||||
expect(store.accentColorAsistencias).toBe('#E91E63') // Default
|
||||
expect(store.accentColorConfiguracion).toBe('#607D8B') // Default
|
||||
// New default view keys should fall back to 'table'
|
||||
expect(store.defaultViewEmpleados).toBe('table')
|
||||
expect(store.defaultViewTareas).toBe('table')
|
||||
expect(store.defaultViewPlanillas).toBe('table')
|
||||
expect(store.defaultViewAsistencias).toBe('table')
|
||||
expect(store.defaultViewConfiguracion).toBe('table')
|
||||
// Other new keys
|
||||
expect(store.tableBgColorEmpleados).toBe('#FFFFFF') // Default
|
||||
expect(store.desktopNavbarPersistent).toBe(false) // Default
|
||||
})
|
||||
|
||||
it('falls back to default settings if localStorage data is invalid JSON', () => {
|
||||
@@ -129,11 +168,14 @@ describe('useUi Store', () => {
|
||||
const appearanceSettingKeysInTest = [
|
||||
'primaryColor', 'secondaryColor', 'warningColor', 'fontFamily',
|
||||
'fontSize', 'animationsEnabled', 'backgroundColor', 'theme',
|
||||
'accentColorEmpleados', 'accentColorTareas', 'accentColorPlanillas', 'accentColorAsistencias',
|
||||
const appearanceSettingKeys = [
|
||||
'primaryColor', 'secondaryColor', 'warningColor', 'fontFamily',
|
||||
'fontSize', 'animationsEnabled', 'backgroundColor', 'theme',
|
||||
]
|
||||
'accentColorEmpleados', 'accentColorTareas', 'accentColorPlanillas',
|
||||
'accentColorAsistencias', 'accentColorConfiguracion',
|
||||
'tableBgColorEmpleados', 'tableBgColorTareas', 'tableBgColorPlanillas',
|
||||
'tableBgColorAsistencias', 'tableBgColorConfiguracion',
|
||||
'desktopNavbarPersistent',
|
||||
'defaultViewEmpleados', 'defaultViewTareas', 'defaultViewPlanillas',
|
||||
'defaultViewAsistencias', 'defaultViewConfiguracion',
|
||||
];
|
||||
|
||||
it('setPrimaryColor updates state and saves to localStorage', () => {
|
||||
const store = useUi()
|
||||
@@ -215,7 +257,9 @@ describe('useUi Store', () => {
|
||||
expect(Object.keys(savedData).length).toBe(appearanceSettingKeysInTest.length);
|
||||
expect(savedData.sidebarOpen).toBeUndefined() // Ensure non-appearance data is not saved
|
||||
appearanceSettingKeysInTest.forEach(key => {
|
||||
expect(savedData.hasOwnProperty(key)).toBe(true)
|
||||
//This assertion needs to be robust if some keys are not initialized (e.g. undefined)
|
||||
//However, our store initializes all appearance keys
|
||||
expect(savedData.hasOwnProperty(key)).toBe(true);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -259,11 +303,100 @@ describe('useUi Store', () => {
|
||||
expect.stringContaining('"accentColorAsistencias":"#FF7788"')
|
||||
)
|
||||
})
|
||||
expect(Object.keys(savedData).length).toBe(appearanceSettingKeys.length);
|
||||
expect(savedData.sidebarOpen).toBeUndefined() // Ensure non-appearance data is not saved
|
||||
appearanceSettingKeys.forEach(key => {
|
||||
expect(savedData.hasOwnProperty(key)).toBe(true)
|
||||
})
|
||||
|
||||
it('setAccentColorConfiguracion updates state and saves to localStorage', () => {
|
||||
const store = useUi()
|
||||
store.setAccentColorConfiguracion('#99AABB')
|
||||
expect(store.accentColorConfiguracion).toBe('#99AABB')
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"accentColorConfiguracion":"#99AABB"')
|
||||
)
|
||||
})
|
||||
|
||||
// Tests for table background color actions
|
||||
it('setTableBgColorEmpleados updates state and saves to localStorage', () => {
|
||||
const store = useUi()
|
||||
store.setTableBgColorEmpleados('#EEECCC')
|
||||
expect(store.tableBgColorEmpleados).toBe('#EEECCC')
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"tableBgColorEmpleados":"#EEECCC"')
|
||||
)
|
||||
})
|
||||
// Similar tests for Tareas, Planillas, Asistencias, Configuracion table bg colors...
|
||||
|
||||
it('setDesktopNavbarPersistent updates state and saves to localStorage', () => {
|
||||
const store = useUi()
|
||||
store.setDesktopNavbarPersistent(true)
|
||||
expect(store.desktopNavbarPersistent).toBe(true)
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"desktopNavbarPersistent":true')
|
||||
)
|
||||
store.setDesktopNavbarPersistent(false)
|
||||
expect(store.desktopNavbarPersistent).toBe(false)
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"desktopNavbarPersistent":false')
|
||||
)
|
||||
})
|
||||
|
||||
// Tests for new default view actions
|
||||
it('setDefaultViewEmpleados updates state and saves to localStorage', () => {
|
||||
const store = useUi()
|
||||
store.setDefaultViewEmpleados('card')
|
||||
expect(store.defaultViewEmpleados).toBe('card')
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"defaultViewEmpleados":"card"')
|
||||
)
|
||||
store.setDefaultViewEmpleados('table')
|
||||
expect(store.defaultViewEmpleados).toBe('table')
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"defaultViewEmpleados":"table"')
|
||||
)
|
||||
})
|
||||
|
||||
it('setDefaultViewTareas updates state and saves to localStorage', () => {
|
||||
const store = useUi()
|
||||
store.setDefaultViewTareas('card')
|
||||
expect(store.defaultViewTareas).toBe('card')
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"defaultViewTareas":"card"')
|
||||
)
|
||||
})
|
||||
|
||||
it('setDefaultViewPlanillas updates state and saves to localStorage', () => {
|
||||
const store = useUi()
|
||||
store.setDefaultViewPlanillas('card')
|
||||
expect(store.defaultViewPlanillas).toBe('card')
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"defaultViewPlanillas":"card"')
|
||||
)
|
||||
})
|
||||
|
||||
it('setDefaultViewAsistencias updates state and saves to localStorage', () => {
|
||||
const store = useUi()
|
||||
store.setDefaultViewAsistencias('card')
|
||||
expect(store.defaultViewAsistencias).toBe('card')
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"defaultViewAsistencias":"card"')
|
||||
)
|
||||
})
|
||||
|
||||
it('setDefaultViewConfiguracion updates state and saves to localStorage', () => {
|
||||
const store = useUi()
|
||||
store.setDefaultViewConfiguracion('card')
|
||||
expect(store.defaultViewConfiguracion).toBe('card')
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
||||
APPEARANCE_STORAGE_KEY,
|
||||
expect.stringContaining('"defaultViewConfiguracion":"card"')
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,6 +24,12 @@ const appearanceSettingKeys = [
|
||||
'tableBgColorAsistencias',
|
||||
'tableBgColorConfiguracion',
|
||||
'desktopNavbarPersistent',
|
||||
// Default module views
|
||||
'defaultViewEmpleados',
|
||||
'defaultViewTareas',
|
||||
'defaultViewPlanillas',
|
||||
'defaultViewAsistencias',
|
||||
'defaultViewConfiguracion',
|
||||
]
|
||||
|
||||
const loadSettingsFromLocalStorage = () => {
|
||||
@@ -92,6 +98,12 @@ export const useUi = defineStore('ui', {
|
||||
tableBgColorAsistencias: '#FFFFFF',
|
||||
tableBgColorConfiguracion: '#FFFFFF',
|
||||
desktopNavbarPersistent: false,
|
||||
// Default module views
|
||||
'defaultViewEmpleados': 'table',
|
||||
'defaultViewTareas': 'table',
|
||||
'defaultViewPlanillas': 'table',
|
||||
'defaultViewAsistencias': 'table',
|
||||
'defaultViewConfiguracion': 'table',
|
||||
}
|
||||
|
||||
const loadedSettings = loadSettingsFromLocalStorage()
|
||||
@@ -203,6 +215,28 @@ export const useUi = defineStore('ui', {
|
||||
setDesktopNavbarPersistent(enabled) {
|
||||
this.desktopNavbarPersistent = !!enabled // Ensure boolean
|
||||
_saveAppearanceState(this)
|
||||
},
|
||||
|
||||
// Actions for default module views
|
||||
setDefaultViewEmpleados(view) {
|
||||
this.defaultViewEmpleados = view
|
||||
_saveAppearanceState(this)
|
||||
},
|
||||
setDefaultViewTareas(view) {
|
||||
this.defaultViewTareas = view
|
||||
_saveAppearanceState(this)
|
||||
},
|
||||
setDefaultViewPlanillas(view) {
|
||||
this.defaultViewPlanillas = view
|
||||
_saveAppearanceState(this)
|
||||
},
|
||||
setDefaultViewAsistencias(view) {
|
||||
this.defaultViewAsistencias = view
|
||||
_saveAppearanceState(this)
|
||||
},
|
||||
setDefaultViewConfiguracion(view) {
|
||||
this.defaultViewConfiguracion = view
|
||||
_saveAppearanceState(this)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<!-- Per-Module Color Settings -->
|
||||
<section class="mb-10 module-settings-group">
|
||||
<h3 class="text-xl font-semibold mb-4 text-[var(--primary-color)] border-b border-[var(--secondary-color)] pb-2">Empleados Module</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||
<div class="setting-item">
|
||||
<label for="accentColorEmpleados" class="block text-sm font-medium mb-1">Accent Color</label>
|
||||
<input type="color" id="accentColorEmpleados" v-model="ui.accentColorEmpleados" @input="ui.setAccentColorEmpleados($event.target.value)"
|
||||
@@ -89,12 +89,20 @@
|
||||
<input type="color" id="tableBgColorEmpleados" v-model="ui.tableBgColorEmpleados" @input="ui.setTableBgColorEmpleados($event.target.value)"
|
||||
class="w-full h-12 p-1 border rounded-lg cursor-pointer shadow-sm hover:opacity-80 transition-opacity border-[var(--secondary-color)] focus:border-[var(--primary-color)] focus:ring-1 focus:ring-[var(--primary-color)]">
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="defaultViewEmpleados" class="block text-sm font-medium mb-1">Default View</label>
|
||||
<select id="defaultViewEmpleados" v-model="ui.defaultViewEmpleados" @change="ui.setDefaultViewEmpleados($event.target.value)"
|
||||
class="w-full p-3 border rounded-lg shadow-sm focus:ring-[var(--primary-color)] focus:border-[var(--primary-color)] transition-all duration-150 ease-in-out bg-white/10 dark:bg-black/10 border-[var(--secondary-color)] hover:border-[var(--primary-color)]">
|
||||
<option value="table">Table</option>
|
||||
<option value="card">Card</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-10 module-settings-group">
|
||||
<h3 class="text-xl font-semibold mb-4 text-[var(--primary-color)] border-b border-[var(--secondary-color)] pb-2">Tareas Module</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||
<div class="setting-item">
|
||||
<label for="accentColorTareas" class="block text-sm font-medium mb-1">Accent Color</label>
|
||||
<input type="color" id="accentColorTareas" v-model="ui.accentColorTareas" @input="ui.setAccentColorTareas($event.target.value)"
|
||||
@@ -105,12 +113,20 @@
|
||||
<input type="color" id="tableBgColorTareas" v-model="ui.tableBgColorTareas" @input="ui.setTableBgColorTareas($event.target.value)"
|
||||
class="w-full h-12 p-1 border rounded-lg cursor-pointer shadow-sm hover:opacity-80 transition-opacity border-[var(--secondary-color)] focus:border-[var(--primary-color)] focus:ring-1 focus:ring-[var(--primary-color)]">
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="defaultViewTareas" class="block text-sm font-medium mb-1">Default View</label>
|
||||
<select id="defaultViewTareas" v-model="ui.defaultViewTareas" @change="ui.setDefaultViewTareas($event.target.value)"
|
||||
class="w-full p-3 border rounded-lg shadow-sm focus:ring-[var(--primary-color)] focus:border-[var(--primary-color)] transition-all duration-150 ease-in-out bg-white/10 dark:bg-black/10 border-[var(--secondary-color)] hover:border-[var(--primary-color)]">
|
||||
<option value="table">Table</option>
|
||||
<option value="card">Card</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-10 module-settings-group">
|
||||
<h3 class="text-xl font-semibold mb-4 text-[var(--primary-color)] border-b border-[var(--secondary-color)] pb-2">Planillas Module</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||
<div class="setting-item">
|
||||
<label for="accentColorPlanillas" class="block text-sm font-medium mb-1">Accent Color</label>
|
||||
<input type="color" id="accentColorPlanillas" v-model="ui.accentColorPlanillas" @input="ui.setAccentColorPlanillas($event.target.value)"
|
||||
@@ -121,12 +137,20 @@
|
||||
<input type="color" id="tableBgColorPlanillas" v-model="ui.tableBgColorPlanillas" @input="ui.setTableBgColorPlanillas($event.target.value)"
|
||||
class="w-full h-12 p-1 border rounded-lg cursor-pointer shadow-sm hover:opacity-80 transition-opacity border-[var(--secondary-color)] focus:border-[var(--primary-color)] focus:ring-1 focus:ring-[var(--primary-color)]">
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="defaultViewPlanillas" class="block text-sm font-medium mb-1">Default View</label>
|
||||
<select id="defaultViewPlanillas" v-model="ui.defaultViewPlanillas" @change="ui.setDefaultViewPlanillas($event.target.value)"
|
||||
class="w-full p-3 border rounded-lg shadow-sm focus:ring-[var(--primary-color)] focus:border-[var(--primary-color)] transition-all duration-150 ease-in-out bg-white/10 dark:bg-black/10 border-[var(--secondary-color)] hover:border-[var(--primary-color)]">
|
||||
<option value="table">Table</option>
|
||||
<option value="card">Card</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-10 module-settings-group">
|
||||
<h3 class="text-xl font-semibold mb-4 text-[var(--primary-color)] border-b border-[var(--secondary-color)] pb-2">Asistencias Module</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||
<div class="setting-item">
|
||||
<label for="accentColorAsistencias" class="block text-sm font-medium mb-1">Accent Color</label>
|
||||
<input type="color" id="accentColorAsistencias" v-model="ui.accentColorAsistencias" @input="ui.setAccentColorAsistencias($event.target.value)"
|
||||
@@ -137,12 +161,20 @@
|
||||
<input type="color" id="tableBgColorAsistencias" v-model="ui.tableBgColorAsistencias" @input="ui.setTableBgColorAsistencias($event.target.value)"
|
||||
class="w-full h-12 p-1 border rounded-lg cursor-pointer shadow-sm hover:opacity-80 transition-opacity border-[var(--secondary-color)] focus:border-[var(--primary-color)] focus:ring-1 focus:ring-[var(--primary-color)]">
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="defaultViewAsistencias" class="block text-sm font-medium mb-1">Default View</label>
|
||||
<select id="defaultViewAsistencias" v-model="ui.defaultViewAsistencias" @change="ui.setDefaultViewAsistencias($event.target.value)"
|
||||
class="w-full p-3 border rounded-lg shadow-sm focus:ring-[var(--primary-color)] focus:border-[var(--primary-color)] transition-all duration-150 ease-in-out bg-white/10 dark:bg-black/10 border-[var(--secondary-color)] hover:border-[var(--primary-color)]">
|
||||
<option value="table">Table</option>
|
||||
<option value="card">Card</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-10 module-settings-group">
|
||||
<h3 class="text-xl font-semibold mb-4 text-[var(--primary-color)] border-b border-[var(--secondary-color)] pb-2">Configuración Module</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||
<div class="setting-item">
|
||||
<label for="accentColorConfiguracion" class="block text-sm font-medium mb-1">Accent Color</label>
|
||||
<input type="color" id="accentColorConfiguracion" v-model="ui.accentColorConfiguracion" @input="ui.setAccentColorConfiguracion($event.target.value)"
|
||||
@@ -153,6 +185,14 @@
|
||||
<input type="color" id="tableBgColorConfiguracion" v-model="ui.tableBgColorConfiguracion" @input="ui.setTableBgColorConfiguracion($event.target.value)"
|
||||
class="w-full h-12 p-1 border rounded-lg cursor-pointer shadow-sm hover:opacity-80 transition-opacity border-[var(--secondary-color)] focus:border-[var(--primary-color)] focus:ring-1 focus:ring-[var(--primary-color)]">
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="defaultViewConfiguracion" class="block text-sm font-medium mb-1">Default View</label>
|
||||
<select id="defaultViewConfiguracion" v-model="ui.defaultViewConfiguracion" @change="ui.setDefaultViewConfiguracion($event.target.value)"
|
||||
class="w-full p-3 border rounded-lg shadow-sm focus:ring-[var(--primary-color)] focus:border-[var(--primary-color)] transition-all duration-150 ease-in-out bg-white/10 dark:bg-black/10 border-[var(--secondary-color)] hover:border-[var(--primary-color)]">
|
||||
<option value="table">Table</option>
|
||||
<option value="card">Card</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -4,89 +4,68 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useUi } from '../../stores/useUi' // Adjust path
|
||||
import SettingsView from '../SettingsView.vue' // Adjust path
|
||||
|
||||
// Helper to create a fresh store for each test or group
|
||||
const getFreshStore = () => {
|
||||
setActivePinia(createPinia())
|
||||
return useUi()
|
||||
}
|
||||
// No global 'store' variable here, it will be test-specific or wrapper-specific
|
||||
// Helper to manage store and wrapper creation for tests
|
||||
const setupTestEnvironment = (initialStoreState = {}) => {
|
||||
setActivePinia(createPinia()); // Ensures a fresh Pinia instance
|
||||
|
||||
// Mock localStorage for the store if not already globally mocked by Vitest setup
|
||||
// Ensure this mock is consistent with how useUi.js uses localStorage
|
||||
const localStorageMock = (() => {
|
||||
let lsStore = { ...initialStoreState }; // Initialize with initialStoreState for loading
|
||||
return {
|
||||
getItem: vi.fn((key) => {
|
||||
const value = lsStore[key];
|
||||
// Pinia/useUi expects JSON string or null
|
||||
return typeof value === 'object' ? JSON.stringify(value) : value;
|
||||
}),
|
||||
setItem: vi.fn((key, value) => {
|
||||
// Store as string, as localStorage does
|
||||
lsStore[key] = value.toString();
|
||||
// Make actual saved string available for inspection if needed
|
||||
// This helps verify what _saveAppearanceState would do
|
||||
if (key === 'appearanceSettings') {
|
||||
lsStore['_raw_appearanceSettings'] = value.toString();
|
||||
}
|
||||
}),
|
||||
clear: vi.fn(() => { lsStore = {}; }),
|
||||
removeItem: vi.fn((key) => { delete lsStore[key]; }),
|
||||
// Helper to get the raw string for assertion
|
||||
getRawItem: (key) => lsStore[key]
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock, writable: true });
|
||||
|
||||
// If initialStoreState is provided, simulate it being in localStorage
|
||||
// The useUi store reads from localStorage upon creation.
|
||||
if (Object.keys(initialStoreState).length > 0) {
|
||||
localStorage.setItem('appearanceSettings', JSON.stringify(initialStoreState));
|
||||
} else {
|
||||
localStorage.removeItem('appearanceSettings'); // Ensure no settings if none provided
|
||||
}
|
||||
|
||||
const store = useUi(); // This will now load from the mocked localStorage
|
||||
|
||||
const wrapper = mount(SettingsView, {
|
||||
global: {
|
||||
// Pinia store is automatically available due to setActivePinia
|
||||
},
|
||||
});
|
||||
|
||||
return { wrapper, store, localStorageMock };
|
||||
};
|
||||
|
||||
|
||||
describe('SettingsView.vue', () => {
|
||||
// No global 'store' variable here, it will be test-specific or wrapper-specific
|
||||
|
||||
beforeEach(() => {
|
||||
// Ensure a fresh Pinia instance is active for each test.
|
||||
setActivePinia(createPinia())
|
||||
|
||||
// Clear localStorage mock before each test
|
||||
// Note: The mock itself is defined globally in this file or via setupFiles in Vitest config.
|
||||
// Re-stubbing it or ensuring its state is clean.
|
||||
const localStorageMock = window.localStorage; // Assuming it's already stubbed globally by Vitest setup or top of file
|
||||
localStorageMock.clear();
|
||||
if (localStorageMock.setItem.mockClear) { // vi.fn() specific
|
||||
localStorageMock.setItem.mockClear();
|
||||
localStorageMock.getItem.mockClear();
|
||||
}
|
||||
})
|
||||
|
||||
// createWrapper now also handles store creation and returns the store for spying if needed
|
||||
const createWrapperAndStore = (initialStoreState = {}) => {
|
||||
if (Object.keys(initialStoreState).length > 0) {
|
||||
localStorage.setItem('appearanceSettings', JSON.stringify(initialStoreState));
|
||||
}
|
||||
|
||||
const currentStore = useUi(); // Store is created AFTER localStorage is set for this test
|
||||
|
||||
const wrapper = mount(SettingsView, {
|
||||
global: {
|
||||
// Pinia is active via setActivePinia, component should pick it up
|
||||
},
|
||||
});
|
||||
return { wrapper, store: currentStore }; // Return store for spying
|
||||
}
|
||||
// Vitest's vi.clearAllMocks() might be useful here if mocks persist unexpectedly
|
||||
// This setupTestEnvironment function already handles fresh store and localStorage mock per call.
|
||||
// If window.localStorage was stubbed globally (e.g. in vitest.setup.js), ensure it's reset.
|
||||
// For this example, setupTestEnvironment manages its own localStorage mock.
|
||||
});
|
||||
|
||||
it('renders all input elements with initial values from store', () => {
|
||||
const { wrapper } = createWrapperAndStore({ // Store is created inside here after LS mock
|
||||
let store
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a new Pinia instance and activate it for each test
|
||||
// This also resets the store state for each test
|
||||
store = getFreshStore()
|
||||
|
||||
// Mock localStorage for the store
|
||||
const localStorageMock = (() => {
|
||||
let lsStore = {}
|
||||
return {
|
||||
getItem: vi.fn((key) => lsStore[key] || null),
|
||||
setItem: vi.fn((key, value) => { lsStore[key] = value.toString() }),
|
||||
clear: vi.fn(() => { lsStore = {} }),
|
||||
removeItem: vi.fn((key) => { delete lsStore[key] }),
|
||||
}
|
||||
})()
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock, writable: true })
|
||||
localStorageMock.clear()
|
||||
})
|
||||
|
||||
const createWrapper = (initialStoreState = {}) => {
|
||||
// Apply initial state to the store if provided
|
||||
// This is a bit of a workaround as direct state mutation isn't ideal,
|
||||
// but for testing initial binding it can be simpler than calling actions.
|
||||
// Alternatively, set up localStorage then init store.
|
||||
if (Object.keys(initialStoreState).length > 0) {
|
||||
localStorage.setItem('appearanceSettings', JSON.stringify(initialStoreState))
|
||||
}
|
||||
// Re-initialize store to pick up mocked localStorage if initialStoreState was set
|
||||
store = getFreshStore()
|
||||
|
||||
return mount(SettingsView, {
|
||||
global: {
|
||||
// plugins: [store.$pinia], // Removed: setActivePinia should make it available
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
it('renders all input elements with initial values from store', () => {
|
||||
const wrapper = createWrapper({
|
||||
const { wrapper } = setupTestEnvironment({
|
||||
primaryColor: '#111111',
|
||||
secondaryColor: '#222222',
|
||||
warningColor: '#333333',
|
||||
@@ -96,148 +75,181 @@ describe('SettingsView.vue', () => {
|
||||
animationsEnabled: false,
|
||||
theme: 'dark',
|
||||
// Add new accent colors for comprehensive initial state testing
|
||||
primaryColor: '#111111',
|
||||
secondaryColor: '#222222',
|
||||
warningColor: '#333333',
|
||||
backgroundColor: '#444444',
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 18,
|
||||
animationsEnabled: false,
|
||||
theme: 'dark',
|
||||
accentColorEmpleados: '#123456',
|
||||
accentColorTareas: '#654321',
|
||||
accentColorPlanillas: '#abcdef',
|
||||
accentColorAsistencias: '#fedcba',
|
||||
})
|
||||
accentColorConfiguracion: '#aabbcc',
|
||||
defaultViewEmpleados: 'card', // New default view
|
||||
defaultViewTareas: 'table',
|
||||
defaultViewPlanillas: 'card',
|
||||
defaultViewAsistencias: 'table',
|
||||
defaultViewConfiguracion: 'card',
|
||||
});
|
||||
|
||||
// Check general appearance settings
|
||||
})
|
||||
|
||||
expect(wrapper.find('input#primaryColor').element.value).toBe('#111111')
|
||||
expect(wrapper.find('input#secondaryColor').element.value).toBe('#222222')
|
||||
expect(wrapper.find('input#primaryColor').element.value).toBe('#111111');
|
||||
expect(wrapper.find('input#secondaryColor').element.value).toBe('#222222');
|
||||
expect(wrapper.find('input#warningColor').element.value).toBe('#333333')
|
||||
expect(wrapper.find('input#backgroundColor').element.value).toBe('#444444')
|
||||
expect(wrapper.find('input#fontFamily').element.value).toBe('Arial')
|
||||
expect(wrapper.find('input#fontSize').element.value).toBe('18')
|
||||
expect(wrapper.find('input#animationsEnabled').element.checked).toBe(false)
|
||||
expect(wrapper.find('select#theme').element.value).toBe('dark')
|
||||
expect(wrapper.find('input#animationsEnabled').element.checked).toBe(false);
|
||||
expect(wrapper.find('select#theme').element.value).toBe('dark');
|
||||
|
||||
// Check new module accent color pickers
|
||||
const h2Elements = wrapper.findAll('h2')
|
||||
const sectionTitles = h2Elements.map(h => h.text())
|
||||
expect(sectionTitles).toContain('Module Accent Colors')
|
||||
expect(wrapper.find('input#accentColorEmpleados').element.value).toBe('#123456')
|
||||
expect(wrapper.find('input#accentColorTareas').element.value).toBe('#654321')
|
||||
expect(wrapper.find('input#accentColorPlanillas').element.value).toBe('#abcdef')
|
||||
expect(wrapper.find('input#accentColorAsistencias').element.value).toBe('#fedcba')
|
||||
})
|
||||
// Check module accent color pickers
|
||||
expect(wrapper.find('input#accentColorEmpleados').element.value).toBe('#123456');
|
||||
expect(wrapper.find('input#accentColorTareas').element.value).toBe('#654321');
|
||||
expect(wrapper.find('input#accentColorPlanillas').element.value).toBe('#abcdef');
|
||||
expect(wrapper.find('input#accentColorAsistencias').element.value).toBe('#fedcba');
|
||||
expect(wrapper.find('input#accentColorConfiguracion').element.value).toBe('#aabbcc');
|
||||
|
||||
// Check new default view select elements
|
||||
expect(wrapper.find('select#defaultViewEmpleados').element.value).toBe('card');
|
||||
expect(wrapper.find('select#defaultViewTareas').element.value).toBe('table');
|
||||
expect(wrapper.find('select#defaultViewPlanillas').element.value).toBe('card');
|
||||
expect(wrapper.find('select#defaultViewAsistencias').element.value).toBe('table');
|
||||
expect(wrapper.find('select#defaultViewConfiguracion').element.value).toBe('card');
|
||||
});
|
||||
|
||||
it('calls setPrimaryColor action when primary color input changes', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore()
|
||||
const spy = vi.spyOn(store, 'setPrimaryColor')
|
||||
const colorInput = wrapper.find('input#primaryColor')
|
||||
|
||||
colorInput.element.value = '#FF00FF'
|
||||
await colorInput.trigger('input')
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('#ff00ff')
|
||||
})
|
||||
const { wrapper, store } = setupTestEnvironment();
|
||||
const spy = vi.spyOn(store, 'setPrimaryColor');
|
||||
const colorInput = wrapper.find('input#primaryColor');
|
||||
colorInput.element.value = '#FF00FF';
|
||||
await colorInput.trigger('input');
|
||||
expect(spy).toHaveBeenCalledWith('#ff00ff');
|
||||
});
|
||||
|
||||
it('calls setFontFamily action when font family input changes', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore()
|
||||
const spy = vi.spyOn(store, 'setFontFamily')
|
||||
const input = wrapper.find('input#fontFamily')
|
||||
await input.setValue('Helvetica')
|
||||
})
|
||||
|
||||
it('calls setPrimaryColor action when primary color input changes', async () => {
|
||||
const wrapper = createWrapper()
|
||||
const spy = vi.spyOn(store, 'setPrimaryColor')
|
||||
const colorInput = wrapper.find('input#primaryColor')
|
||||
|
||||
// Simulate color picker actually setting the value and then dispatching input
|
||||
// For input type=color, setting .value and then .trigger('input') is typical
|
||||
colorInput.element.value = '#FF00FF'
|
||||
await colorInput.trigger('input')
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('#ff00ff') // Changed to lowercase
|
||||
})
|
||||
|
||||
it('calls setFontFamily action when font family input changes', async () => {
|
||||
const wrapper = createWrapper()
|
||||
const spy = vi.spyOn(store, 'setFontFamily')
|
||||
const input = wrapper.find('input#fontFamily')
|
||||
await input.setValue('Helvetica') // .setValue also triggers 'input'
|
||||
expect(spy).toHaveBeenCalledWith('Helvetica')
|
||||
})
|
||||
const { wrapper, store } = setupTestEnvironment();
|
||||
const spy = vi.spyOn(store, 'setFontFamily');
|
||||
const input = wrapper.find('input#fontFamily');
|
||||
await input.setValue('Helvetica');
|
||||
expect(spy).toHaveBeenCalledWith('Helvetica');
|
||||
});
|
||||
|
||||
it('calls setFontSize action when font size input changes', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore()
|
||||
const wrapper = createWrapper()
|
||||
const spy = vi.spyOn(store, 'setFontSize')
|
||||
const input = wrapper.find('input#fontSize')
|
||||
await input.setValue('22')
|
||||
expect(spy).toHaveBeenCalledWith(22)
|
||||
})
|
||||
const { wrapper, store } = setupTestEnvironment();
|
||||
const spy = vi.spyOn(store, 'setFontSize');
|
||||
const input = wrapper.find('input#fontSize');
|
||||
await input.setValue('22');
|
||||
expect(spy).toHaveBeenCalledWith(22);
|
||||
});
|
||||
|
||||
it('calls setAnimationsEnabled action when animations checkbox changes', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore({ animationsEnabled: true })
|
||||
const spy = vi.spyOn(store, 'setAnimationsEnabled')
|
||||
const checkbox = wrapper.find('input#animationsEnabled')
|
||||
|
||||
await checkbox.setChecked(false)
|
||||
const wrapper = createWrapper({ animationsEnabled: true }) // Start with true
|
||||
const spy = vi.spyOn(store, 'setAnimationsEnabled')
|
||||
const checkbox = wrapper.find('input#animationsEnabled')
|
||||
|
||||
// For checkboxes, .setValue(false) or .setChecked(false) and then trigger 'change'
|
||||
await checkbox.setChecked(false) // This should trigger the change event for v-model
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(false)
|
||||
})
|
||||
const { wrapper, store } = setupTestEnvironment({ animationsEnabled: true });
|
||||
const spy = vi.spyOn(store, 'setAnimationsEnabled');
|
||||
const checkbox = wrapper.find('input#animationsEnabled');
|
||||
await checkbox.setChecked(false);
|
||||
expect(spy).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('calls setTheme action when theme select changes', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore()
|
||||
const spy = vi.spyOn(store, 'setTheme')
|
||||
const select = wrapper.find('select#theme')
|
||||
await select.setValue('dark')
|
||||
expect(spy).toHaveBeenCalledWith('dark')
|
||||
})
|
||||
const { wrapper, store } = setupTestEnvironment();
|
||||
const spy = vi.spyOn(store, 'setTheme');
|
||||
const select = wrapper.find('select#theme');
|
||||
await select.setValue('dark');
|
||||
expect(spy).toHaveBeenCalledWith('dark');
|
||||
});
|
||||
|
||||
// New tests for module accent color pickers
|
||||
// Tests for module accent color pickers
|
||||
it('calls setAccentColorEmpleados action when empleados accent color input changes', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore()
|
||||
const spy = vi.spyOn(store, 'setAccentColorEmpleados')
|
||||
const colorInput = wrapper.find('input#accentColorEmpleados')
|
||||
colorInput.element.value = '#aabbcc'
|
||||
await colorInput.trigger('input')
|
||||
expect(spy).toHaveBeenCalledWith('#aabbcc')
|
||||
})
|
||||
const { wrapper, store } = setupTestEnvironment();
|
||||
const spy = vi.spyOn(store, 'setAccentColorEmpleados');
|
||||
const colorInput = wrapper.find('input#accentColorEmpleados');
|
||||
colorInput.element.value = '#aabbcc';
|
||||
await colorInput.trigger('input');
|
||||
expect(spy).toHaveBeenCalledWith('#aabbcc');
|
||||
});
|
||||
|
||||
it('calls setAccentColorTareas action when tareas accent color input changes', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore()
|
||||
const spy = vi.spyOn(store, 'setAccentColorTareas')
|
||||
const colorInput = wrapper.find('input#accentColorTareas')
|
||||
colorInput.element.value = '#ccbbaa'
|
||||
await colorInput.trigger('input')
|
||||
expect(spy).toHaveBeenCalledWith('#ccbbaa')
|
||||
})
|
||||
it('calls setAccentColorConfiguracion action when configuracion accent color input changes', async () => {
|
||||
const { wrapper, store } = setupTestEnvironment();
|
||||
const spy = vi.spyOn(store, 'setAccentColorConfiguracion');
|
||||
const colorInput = wrapper.find('input#accentColorConfiguracion');
|
||||
colorInput.element.value = '#ccddee';
|
||||
await colorInput.trigger('input');
|
||||
expect(spy).toHaveBeenCalledWith('#ccddee');
|
||||
});
|
||||
|
||||
it('calls setAccentColorPlanillas action when planillas accent color input changes', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore()
|
||||
const spy = vi.spyOn(store, 'setAccentColorPlanillas')
|
||||
const colorInput = wrapper.find('input#accentColorPlanillas')
|
||||
colorInput.element.value = '#a1b2c3'
|
||||
await colorInput.trigger('input')
|
||||
expect(spy).toHaveBeenCalledWith('#a1b2c3')
|
||||
})
|
||||
|
||||
it('calls setAccentColorAsistencias action when asistencias accent color input changes', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore()
|
||||
const spy = vi.spyOn(store, 'setAccentColorAsistencias')
|
||||
const colorInput = wrapper.find('input#accentColorAsistencias')
|
||||
colorInput.element.value = '#c3b2a1'
|
||||
await colorInput.trigger('input')
|
||||
expect(spy).toHaveBeenCalledWith('#c3b2a1')
|
||||
})
|
||||
// *** ADD NEW TESTS FOR DEFAULT VIEW SELECTS HERE ***
|
||||
describe('Default View Selects', () => {
|
||||
it('renders default view select for Empleados module and calls action on change', async () => {
|
||||
const { wrapper, store } = setupTestEnvironment({ defaultViewEmpleados: 'table' });
|
||||
const select = wrapper.find('select#defaultViewEmpleados');
|
||||
expect(select.exists()).toBe(true);
|
||||
expect(select.element.value).toBe('table');
|
||||
const options = select.findAll('option');
|
||||
expect(options.length).toBe(2);
|
||||
expect(options[0].text()).toBe('Table');
|
||||
expect(options[0].element.value).toBe('table');
|
||||
expect(options[1].text()).toBe('Card');
|
||||
expect(options[1].element.value).toBe('card');
|
||||
|
||||
const spy = vi.spyOn(store, 'setDefaultViewEmpleados');
|
||||
await select.setValue('card');
|
||||
expect(spy).toHaveBeenCalledWith('card');
|
||||
expect(select.element.value).toBe('card'); // Assuming v-model updates from store mock correctly
|
||||
});
|
||||
|
||||
it('renders default view select for Tareas module and calls action on change', async () => {
|
||||
const { wrapper, store } = setupTestEnvironment({ defaultViewTareas: 'table' });
|
||||
const select = wrapper.find('select#defaultViewTareas');
|
||||
expect(select.exists()).toBe(true);
|
||||
expect(select.element.value).toBe('table');
|
||||
const spy = vi.spyOn(store, 'setDefaultViewTareas');
|
||||
await select.setValue('card');
|
||||
expect(spy).toHaveBeenCalledWith('card');
|
||||
expect(select.element.value).toBe('card');
|
||||
});
|
||||
|
||||
it('renders default view select for Planillas module and calls action on change', async () => {
|
||||
const { wrapper, store } = setupTestEnvironment({ defaultViewPlanillas: 'table' });
|
||||
const select = wrapper.find('select#defaultViewPlanillas');
|
||||
expect(select.exists()).toBe(true);
|
||||
expect(select.element.value).toBe('table');
|
||||
const spy = vi.spyOn(store, 'setDefaultViewPlanillas');
|
||||
await select.setValue('card');
|
||||
expect(spy).toHaveBeenCalledWith('card');
|
||||
expect(select.element.value).toBe('card');
|
||||
});
|
||||
|
||||
it('renders default view select for Asistencias module and calls action on change', async () => {
|
||||
const { wrapper, store } = setupTestEnvironment({ defaultViewAsistencias: 'table' });
|
||||
const select = wrapper.find('select#defaultViewAsistencias');
|
||||
expect(select.exists()).toBe(true);
|
||||
expect(select.element.value).toBe('table');
|
||||
const spy = vi.spyOn(store, 'setDefaultViewAsistencias');
|
||||
await select.setValue('card');
|
||||
expect(spy).toHaveBeenCalledWith('card');
|
||||
expect(select.element.value).toBe('card');
|
||||
});
|
||||
|
||||
it('renders default view select for Configuracion module and calls action on change', async () => {
|
||||
const { wrapper, store } = setupTestEnvironment({ defaultViewConfiguracion: 'table' });
|
||||
const select = wrapper.find('select#defaultViewConfiguracion');
|
||||
expect(select.exists()).toBe(true);
|
||||
expect(select.element.value).toBe('table');
|
||||
const spy = vi.spyOn(store, 'setDefaultViewConfiguracion');
|
||||
await select.setValue('card');
|
||||
expect(spy).toHaveBeenCalledWith('card');
|
||||
expect(select.element.value).toBe('card');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('updates input values when store state changes programmatically', async () => {
|
||||
const { wrapper, store } = createWrapperAndStore() // Store instance for this test
|
||||
const { wrapper, store } = setupTestEnvironment();
|
||||
|
||||
// Test primaryColor
|
||||
store.primaryColor = '#001122' // Directly manipulate the store used by the component
|
||||
store.primaryColor = '#001122';
|
||||
=======
|
||||
const wrapper = createWrapper()
|
||||
const spy = vi.spyOn(store, 'setTheme')
|
||||
@@ -251,51 +263,43 @@ describe('SettingsView.vue', () => {
|
||||
|
||||
// Test primaryColor
|
||||
store.primaryColor = '#001122'
|
||||
await wrapper.vm.$nextTick() // Wait for Vue to react to state change
|
||||
expect(wrapper.find('input#primaryColor').element.value).toBe('#001122')
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('input#primaryColor').element.value).toBe('#001122');
|
||||
|
||||
// Test fontFamily
|
||||
store.fontFamily = 'Verdana'
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('input#fontFamily').element.value).toBe('Verdana')
|
||||
store.fontFamily = 'Verdana';
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('input#fontFamily').element.value).toBe('Verdana');
|
||||
|
||||
// Test fontSize
|
||||
store.fontSize = 12
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('input#fontSize').element.value).toBe('12')
|
||||
store.fontSize = 12;
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('input#fontSize').element.value).toBe('12');
|
||||
|
||||
// Test animationsEnabled
|
||||
store.animationsEnabled = false
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('input#animationsEnabled').element.checked).toBe(false)
|
||||
store.animationsEnabled = false;
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('input#animationsEnabled').element.checked).toBe(false);
|
||||
|
||||
// Test theme
|
||||
store.theme = 'dark'
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('select#theme').element.value).toBe('dark')
|
||||
store.theme = 'dark';
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('select#theme').element.value).toBe('dark');
|
||||
|
||||
// Test one of the new accent colors
|
||||
store.accentColorEmpleados = '#998877'
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('input#accentColorEmpleados').element.value).toBe('#998877')
|
||||
})
|
||||
store.accentColorEmpleados = '#998877';
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('input#accentColorEmpleados').element.value).toBe('#998877');
|
||||
|
||||
// Test one of the new default view selects
|
||||
store.defaultViewEmpleados = 'card';
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('select#defaultViewEmpleados').element.value).toBe('card');
|
||||
});
|
||||
|
||||
// Test for the initial fade-in animation - checking class
|
||||
it('applies opacity transition class after mount', async () => {
|
||||
// Mock setTimeout to control its execution
|
||||
vi.useFakeTimers()
|
||||
vi.useFakeTimers();
|
||||
const { wrapper } = setupTestEnvironment();
|
||||
expect(wrapper.find('.settings-view').classes()).not.toContain('opacity-100');
|
||||
|
||||
const { wrapper } = createWrapperAndStore() // Use the new function name
|
||||
const wrapper = createWrapper()
|
||||
expect(wrapper.find('.settings-view').classes()).not.toContain('opacity-100')
|
||||
|
||||
// Advance timers by the amount used in setTimeout in SettingsView.vue (50ms)
|
||||
vi.advanceTimersByTime(100) // Advance a bit more to be sure
|
||||
await wrapper.vm.$nextTick() // Allow Vue to re-render
|
||||
|
||||
expect(wrapper.find('.settings-view').classes()).toContain('opacity-100')
|
||||
|
||||
vi.useRealTimers() // Restore real timers
|
||||
})
|
||||
|
||||
})
|
||||
vi.advanceTimersByTime(100);
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('.settings-view').classes()).toContain('opacity-100');
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,13 +7,23 @@
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="mb-6 flex justify-end items-center space-x-3">
|
||||
<span class="text-sm font-medium text-gray-700">Cambiar Vista:</span>
|
||||
<button @click="currentView = 'card'" :class="btnClass('card')">
|
||||
Tarjetas
|
||||
<!-- View Toggle Buttons -->
|
||||
<div class="mb-4 flex justify-end space-x-2">
|
||||
<button
|
||||
@click="currentView = 'table'"
|
||||
:class="btnViewClass('table')"
|
||||
aria-label="Table View"
|
||||
title="Table View"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
||||
</button>
|
||||
<button @click="currentView = 'table'" :class="btnClass('table')">
|
||||
Tabla
|
||||
<button
|
||||
@click="currentView = 'card'"
|
||||
:class="btnViewClass('card')"
|
||||
aria-label="Card View"
|
||||
title="Card View"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -60,18 +70,21 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useAsistenciasStore } from '../../stores/useAsistencias';
|
||||
import { useUi } from '../../stores/useUi'; // Import useUi
|
||||
import { useRouter } from 'vue-router';
|
||||
import TablaAsistencias from '../../components/asistencias/tablaAsistencias.vue';
|
||||
import CardAsistencia from '../../components/asistencias/cardAsistencia.vue';
|
||||
|
||||
const asistenciasStore = useAsistenciasStore();
|
||||
const ui = useUi(); // Access the ui store
|
||||
const router = useRouter();
|
||||
|
||||
const isLoading = ref(true);
|
||||
const errorLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
const currentView = ref('table'); // Default to table view
|
||||
// Initialize currentView from the store's default setting for asistencias
|
||||
const currentView = ref(ui.defaultViewAsistencias);
|
||||
|
||||
const asistenciasList = computed(() => asistenciasStore.asistencias);
|
||||
|
||||
@@ -100,12 +113,12 @@ const handleEditAsistencia = (asistenciaId) => {
|
||||
router.push({ name: 'asistencias-edit', params: { id: asistenciaId } });
|
||||
};
|
||||
|
||||
const btnClass = (view) => {
|
||||
const baseClasses = 'px-4 py-2 rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2';
|
||||
if (currentView.value === view) {
|
||||
return `${baseClasses} text-white shadow-sm view-toggle-active-asistencias`;
|
||||
const btnViewClass = (viewType) => {
|
||||
const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out';
|
||||
if (currentView.value === viewType) {
|
||||
return `${base} bg-[var(--accent-color-asistencias)] text-white shadow-lg`;
|
||||
}
|
||||
return `${baseClasses} bg-gray-200 text-gray-700 hover:bg-gray-300 focus:ring-gray-400`;
|
||||
return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
216
ui/src/views/asistencias/__tests__/AsistenciasIndex.spec.js
Normal file
216
ui/src/views/asistencias/__tests__/AsistenciasIndex.spec.js
Normal file
@@ -0,0 +1,216 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useUi } from '@/stores/useUi'
|
||||
import { useAsistenciasStore } from '@/stores/useAsistencias'
|
||||
import AsistenciasIndex from '../AsistenciasIndex.vue'
|
||||
import TablaAsistencias from '@/components/asistencias/tablaAsistencias.vue'
|
||||
import CardAsistencia from '@/components/asistencias/cardAsistencia.vue'
|
||||
|
||||
// Mock child components
|
||||
vi.mock('@/components/asistencias/tablaAsistencias.vue', () => ({
|
||||
default: {
|
||||
name: 'TablaAsistencias',
|
||||
props: ['asistencias', 'isLoading'], // Match actual props if different
|
||||
emits: ['edit'],
|
||||
template: '<div data-testid="tabla-asistencias"></div>',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/components/asistencias/cardAsistencia.vue', () => ({
|
||||
default: {
|
||||
name: 'CardAsistencia',
|
||||
props: ['asistencia'], // Match actual props
|
||||
emits: ['edit'],
|
||||
template: '<div data-testid="card-asistencia"></div>',
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock stores
|
||||
const mockSetDefaultViewAsistencias = vi.fn();
|
||||
const mockFetchAsistencias = vi.fn();
|
||||
|
||||
vi.mock('@/stores/useUi', () => ({
|
||||
useUi: vi.fn(() => ({
|
||||
defaultViewAsistencias: 'table', // Default mock value
|
||||
setDefaultViewAsistencias: mockSetDefaultViewAsistencias,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/useAsistencias', () => ({
|
||||
useAsistenciasStore: vi.fn(() => ({
|
||||
asistencias: [],
|
||||
fetchAsistencias: mockFetchAsistencias,
|
||||
})),
|
||||
}))
|
||||
|
||||
describe('AsistenciasIndex.vue', () => {
|
||||
let uiStoreMock
|
||||
let asistenciasStoreMock
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
mockFetchAsistencias.mockClear().mockResolvedValue([])
|
||||
mockSetDefaultViewAsistencias.mockClear()
|
||||
|
||||
uiStoreMock = useUi()
|
||||
asistenciasStoreMock = useAsistenciasStore()
|
||||
})
|
||||
|
||||
const mountComponent = () => {
|
||||
return mount(AsistenciasIndex, {
|
||||
global: {
|
||||
// Stubs can be defined here too if not using vi.mock for everything
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
it('fetches asistencias on mount', async () => {
|
||||
mountComponent()
|
||||
// It seems the component has its own isLoading ref, wait for it to resolve
|
||||
await mockFetchAsistencias(); // Ensure the promise resolves
|
||||
expect(mockFetchAsistencias).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('View Rendering based on useUi store', () => {
|
||||
it('renders TablaAsistencias when defaultViewAsistencias is "table"', async () => {
|
||||
uiStoreMock.defaultViewAsistencias = 'table'
|
||||
asistenciasStoreMock.asistencias = [{ id: 1, empleado: 'Test Employee', fecha: '2023-01-01' }]
|
||||
|
||||
const wrapper = mountComponent()
|
||||
// Wait for internal loading state and then data processing
|
||||
await mockFetchAsistencias();
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaAsistencias' }).exists()).toBe(true)
|
||||
expect(wrapper.findComponent({ name: 'TablaAsistencias' }).props('asistencias')).toEqual(asistenciasStoreMock.asistencias)
|
||||
expect(wrapper.findComponent({ name: 'CardAsistencia' }).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders CardAsistencia when defaultViewAsistencias is "card"', async () => {
|
||||
uiStoreMock.defaultViewAsistencias = 'card'
|
||||
asistenciasStoreMock.asistencias = [{ id: 1, empleado: 'E1' }, { id: 2, empleado: 'E2' }]
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await mockFetchAsistencias();
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const cardWrappers = wrapper.findAllComponents({ name: 'CardAsistencia' })
|
||||
expect(cardWrappers.length).toBe(asistenciasStoreMock.asistencias.length)
|
||||
expect(cardWrappers[0].props('asistencia')).toEqual(asistenciasStoreMock.asistencias[0])
|
||||
expect(wrapper.findComponent({ name: 'TablaAsistencias' }).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders no data message for table view when no asistencias exist', async () => {
|
||||
uiStoreMock.defaultViewAsistencias = 'table';
|
||||
asistenciasStoreMock.asistencias = [];
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchAsistencias();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaAsistencias' }).exists()).toBe(true);
|
||||
// The component might show "Cargando asistencias..." initially, then the no data message.
|
||||
// We need to ensure loading is complete.
|
||||
expect(wrapper.text()).toContain('No hay asistencias para mostrar');
|
||||
});
|
||||
|
||||
it('renders no data message for card view when no asistencias exist', async () => {
|
||||
uiStoreMock.defaultViewAsistencias = 'card';
|
||||
asistenciasStoreMock.asistencias = [];
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchAsistencias();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findAllComponents({ name: 'CardAsistencia' }).length).toBe(0);
|
||||
expect(wrapper.text()).toContain('No hay asistencias para mostrar');
|
||||
});
|
||||
})
|
||||
|
||||
describe('Local View Toggle Buttons', () => {
|
||||
it('renders toggle buttons and reflects initial view from store (table)', async () => {
|
||||
uiStoreMock.defaultViewAsistencias = 'table';
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchAsistencias();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
|
||||
expect(tableViewButton.exists()).toBe(true);
|
||||
expect(cardViewButton.exists()).toBe(true);
|
||||
|
||||
// Check active class based on btnViewClass logic
|
||||
// Active: bg-[var(--accent-color-asistencias)] text-white
|
||||
// Inactive: bg-gray-200 text-gray-700
|
||||
expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]');
|
||||
expect(cardViewButton.classes()).toContain('bg-gray-200');
|
||||
});
|
||||
|
||||
it('renders toggle buttons and reflects initial view from store (card)', async () => {
|
||||
uiStoreMock.defaultViewAsistencias = 'card';
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchAsistencias();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]');
|
||||
expect(tableViewButton.classes()).toContain('bg-gray-200');
|
||||
});
|
||||
|
||||
it('switches to card view on button click and updates button styles, does not call global store action', async () => {
|
||||
uiStoreMock.defaultViewAsistencias = 'table';
|
||||
asistenciasStoreMock.asistencias = [{ id: 1, empleado: 'Test' }];
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchAsistencias();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
|
||||
await cardViewButton.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'CardAsistencia' }).exists()).toBe(true);
|
||||
expect(wrapper.findComponent({ name: 'TablaAsistencias' }).exists()).toBe(false);
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]');
|
||||
expect(tableViewButton.classes()).toContain('bg-gray-200');
|
||||
expect(mockSetDefaultViewAsistencias).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('switches back to table view on button click and updates button styles, does not call global store action', async () => {
|
||||
uiStoreMock.defaultViewAsistencias = 'card'; // Start with card view
|
||||
asistenciasStoreMock.asistencias = [{ id: 1, empleado: 'Test' }];
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchAsistencias();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Initially card view is active
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]');
|
||||
|
||||
|
||||
await tableViewButton.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaAsistencias' }).exists()).toBe(true);
|
||||
expect(wrapper.findComponent({ name: 'CardAsistencia' }).exists()).toBe(false);
|
||||
expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]');
|
||||
expect(cardViewButton.classes()).toContain('bg-gray-200');
|
||||
expect(mockSetDefaultViewAsistencias).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -19,19 +19,22 @@
|
||||
</header>
|
||||
|
||||
<!-- selector de vista -->
|
||||
<div class="mb-6 flex justify-end items-center space-x-3">
|
||||
<span class="text-sm font-medium text-gray-700">Cambiar Vista:</span>
|
||||
<button
|
||||
@click="currentView = 'card'"
|
||||
:class="btnClass('card')"
|
||||
>
|
||||
Tarjetas
|
||||
</button>
|
||||
<div class="mb-4 flex justify-end space-x-2">
|
||||
<button
|
||||
@click="currentView = 'table'"
|
||||
:class="btnClass('table')"
|
||||
:class="btnViewClass('table')"
|
||||
aria-label="Table View"
|
||||
title="Table View"
|
||||
>
|
||||
Tabla
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
||||
</button>
|
||||
<button
|
||||
@click="currentView = 'card'"
|
||||
:class="btnViewClass('card')"
|
||||
aria-label="Card View"
|
||||
title="Card View"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -80,6 +83,7 @@
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUi } from '@/stores/useUi'; // Import useUi
|
||||
|
||||
import CardEmpleado from '@/components/empleados/cardEmpleado.vue';
|
||||
import TablaEmpleados from '@/components/empleados/tablaEmpleados.vue';
|
||||
@@ -87,7 +91,8 @@ import { useEmpleadosStore } from '@/stores/useEmpleados.js'; // ruta según tu
|
||||
|
||||
// --- refs locales ---
|
||||
const router = useRouter();
|
||||
const currentView = ref<'card' | 'table'>('card');
|
||||
const ui = useUi(); // Access the ui store
|
||||
const currentView = ref<'card' | 'table'>(ui.defaultViewEmpleados); // Initialize from store
|
||||
const loading = ref(true);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
@@ -99,14 +104,13 @@ const { empleados } = storeToRefs(empleadosStore);
|
||||
const employees = empleados;
|
||||
|
||||
// --- helpers ---
|
||||
const btnClass = (view: 'card' | 'table') => {
|
||||
const baseClasses = 'px-4 py-2 rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2';
|
||||
if (currentView.value === view) {
|
||||
// Active button uses accent color
|
||||
return `${baseClasses} text-white shadow-sm view-toggle-active`;
|
||||
// Removed btnClass as manual toggle buttons are removed
|
||||
const btnViewClass = (viewType: 'card' | 'table') => {
|
||||
const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out';
|
||||
if (currentView.value === viewType) {
|
||||
return `${base} bg-[var(--accent-color-empleados)] text-white shadow-lg`;
|
||||
}
|
||||
// Inactive button uses secondary/gray styling
|
||||
return `${baseClasses} bg-gray-200 text-gray-700 hover:bg-gray-300 focus:ring-gray-400`;
|
||||
return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`;
|
||||
};
|
||||
|
||||
// --- fetch inicial ---
|
||||
|
||||
208
ui/src/views/empleados/__tests__/EmpleadosIndex.spec.js
Normal file
208
ui/src/views/empleados/__tests__/EmpleadosIndex.spec.js
Normal file
@@ -0,0 +1,208 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useUi } from '@/stores/useUi'
|
||||
import { useEmpleadosStore } from '@/stores/useEmpleados'
|
||||
import EmpleadosIndex from '../EmpleadosIndex.vue'
|
||||
import TablaEmpleados from '@/components/empleados/tablaEmpleados.vue'
|
||||
import CardEmpleado from '@/components/empleados/cardEmpleado.vue'
|
||||
|
||||
// Mock the child components to simplify testing and focus on EmpleadosIndex logic
|
||||
vi.mock('@/components/empleados/tablaEmpleados.vue', () => ({
|
||||
default: {
|
||||
name: 'TablaEmpleados',
|
||||
props: ['employees'],
|
||||
template: '<div data-testid="tabla-empleados"></div>',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/components/empleados/cardEmpleado.vue', () => ({
|
||||
default: {
|
||||
name: 'CardEmpleado',
|
||||
props: ['employee'],
|
||||
template: '<div data-testid="card-empleado"></div>',
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock the stores
|
||||
// We need to define the mock functions before they are used by `vi.mock`
|
||||
const mockSetDefaultViewEmpleados = vi.fn();
|
||||
const mockFetchEmpleados = vi.fn();
|
||||
|
||||
vi.mock('@/stores/useUi', () => ({
|
||||
useUi: vi.fn(() => ({
|
||||
defaultViewEmpleados: 'table', // Default mock value
|
||||
// Add any other state or actions needed by the component from useUi
|
||||
setDefaultViewEmpleados: mockSetDefaultViewEmpleados,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/useEmpleados', () => ({
|
||||
useEmpleadosStore: vi.fn(() => ({
|
||||
empleados: [],
|
||||
fetchEmpleados: mockFetchEmpleados,
|
||||
// Add other state or actions if EmpleadosIndex uses them
|
||||
})),
|
||||
}))
|
||||
|
||||
describe('EmpleadosIndex.vue', () => {
|
||||
let uiStoreMock
|
||||
let empleadosStoreMock
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
|
||||
// Reset mocks and provide fresh instances for each test
|
||||
mockFetchEmpleados.mockClear().mockResolvedValue([]); // Default to resolve successfully
|
||||
mockSetDefaultViewEmpleados.mockClear();
|
||||
|
||||
// Get fresh instances of the mocked stores for manipulation in tests
|
||||
// The actual `useUi` and `useEmpleadosStore` will be the vi.fn defined above
|
||||
// Re-calling them ensures we can configure their return values per test suite if needed
|
||||
// or rely on the default mock implementation.
|
||||
uiStoreMock = useUi()
|
||||
empleadosStoreMock = useEmpleadosStore()
|
||||
})
|
||||
|
||||
const mountComponent = () => {
|
||||
return mount(EmpleadosIndex, {
|
||||
global: {
|
||||
stubs: {
|
||||
// While components are mocked via vi.mock, explicit stubs can be used for further control if needed
|
||||
// For instance, if you didn't want to mock the entire module.
|
||||
// 'TablaEmpleados': true,
|
||||
// 'CardEmpleado': true,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
it('fetches employees on mount', () => {
|
||||
mountComponent()
|
||||
expect(mockFetchEmpleados).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('View Rendering based on useUi store', () => {
|
||||
it('renders TablaEmpleados when defaultViewEmpleados is "table"', async () => {
|
||||
uiStoreMock.defaultViewEmpleados = 'table' // Set store state for this test
|
||||
empleadosStoreMock.empleados = [{ id: 1, nombre: 'Test Employee' }] // Provide some data
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await wrapper.vm.$nextTick() // Wait for any reactivity updates
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaEmpleados' }).exists()).toBe(true)
|
||||
expect(wrapper.findComponent({ name: 'TablaEmpleados' }).props('employees')).toEqual(empleadosStoreMock.empleados)
|
||||
expect(wrapper.findComponent({ name: 'CardEmpleado' }).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders CardEmpleado when defaultViewEmpleados is "card"', async () => {
|
||||
uiStoreMock.defaultViewEmpleados = 'card' // Set store state for this test
|
||||
empleadosStoreMock.empleados = [{ id: 1, nombre: 'Test Employee' }, { id: 2, nombre: 'Another Employee' }]
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const cardWrappers = wrapper.findAllComponents({ name: 'CardEmpleado' })
|
||||
expect(cardWrappers.length).toBe(empleadosStoreMock.empleados.length)
|
||||
expect(cardWrappers[0].props('employee')).toEqual(empleadosStoreMock.empleados[0])
|
||||
expect(wrapper.findComponent({ name: 'TablaEmpleados' }).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders no data message for table view when no employees exist', async () => {
|
||||
uiStoreMock.defaultViewEmpleados = 'table';
|
||||
empleadosStoreMock.empleados = []; // No data
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await wrapper.vm.$nextTick(); // allow loading state to pass
|
||||
await wrapper.vm.$nextTick(); // allow conditional rendering based on data
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaEmpleados' }).exists()).toBe(true); // Table component itself still renders
|
||||
expect(wrapper.text()).toContain('No hay empleados para mostrar en la vista de tabla.');
|
||||
});
|
||||
|
||||
it('renders no data message for card view when no employees exist', async () => {
|
||||
uiStoreMock.defaultViewEmpleados = 'card';
|
||||
empleadosStoreMock.empleados = []; // No data
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findAllComponents({ name: 'CardEmpleado' }).length).toBe(0);
|
||||
expect(wrapper.text()).toContain('No hay empleados para mostrar en la vista de tarjetas.');
|
||||
});
|
||||
})
|
||||
|
||||
describe('Local View Toggle Buttons', () => {
|
||||
it('renders toggle buttons and reflects initial view from store (table)', async () => {
|
||||
uiStoreMock.defaultViewEmpleados = 'table';
|
||||
const wrapper = mountComponent();
|
||||
// Wait for loading and reactivity
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
|
||||
expect(tableViewButton.exists()).toBe(true);
|
||||
expect(cardViewButton.exists()).toBe(true);
|
||||
expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]');
|
||||
expect(cardViewButton.classes()).toContain('bg-gray-200');
|
||||
});
|
||||
|
||||
it('renders toggle buttons and reflects initial view from store (card)', async () => {
|
||||
uiStoreMock.defaultViewEmpleados = 'card';
|
||||
const wrapper = mountComponent();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]');
|
||||
expect(tableViewButton.classes()).toContain('bg-gray-200');
|
||||
});
|
||||
|
||||
it('switches to card view on button click and updates button styles, does not call global store action', async () => {
|
||||
uiStoreMock.defaultViewEmpleados = 'table';
|
||||
empleadosStoreMock.empleados = [{ id: 1, nombre: 'Test' }];
|
||||
const wrapper = mountComponent();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
|
||||
await cardViewButton.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'CardEmpleado' }).exists()).toBe(true);
|
||||
expect(wrapper.findComponent({ name: 'TablaEmpleados' }).exists()).toBe(false);
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]');
|
||||
expect(tableViewButton.classes()).toContain('bg-gray-200');
|
||||
expect(mockSetDefaultViewEmpleados).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('switches back to table view on button click and updates button styles, does not call global store action', async () => {
|
||||
uiStoreMock.defaultViewEmpleados = 'card'; // Start with card view
|
||||
empleadosStoreMock.empleados = [{ id: 1, nombre: 'Test' }];
|
||||
const wrapper = mountComponent();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]');
|
||||
|
||||
await tableViewButton.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaEmpleados' }).exists()).toBe(true);
|
||||
expect(wrapper.findComponent({ name: 'CardEmpleado' }).exists()).toBe(false);
|
||||
expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]');
|
||||
expect(cardViewButton.classes()).toContain('bg-gray-200');
|
||||
expect(mockSetDefaultViewEmpleados).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -7,13 +7,23 @@
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="mb-6 flex justify-end items-center space-x-3">
|
||||
<span class="text-sm font-medium text-gray-700">Cambiar Vista:</span>
|
||||
<button @click="currentView = 'card'" :class="btnClass('card')">
|
||||
Tarjetas
|
||||
<!-- View Toggle Buttons -->
|
||||
<div class="mb-4 flex justify-end space-x-2">
|
||||
<button
|
||||
@click="currentView = 'table'"
|
||||
:class="btnViewClass('table')"
|
||||
aria-label="Table View"
|
||||
title="Table View"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
||||
</button>
|
||||
<button @click="currentView = 'table'" :class="btnClass('table')">
|
||||
Tabla
|
||||
<button
|
||||
@click="currentView = 'card'"
|
||||
:class="btnViewClass('card')"
|
||||
aria-label="Card View"
|
||||
title="Card View"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -60,18 +70,21 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { usePlanillasStore } from '../../stores/usePlanillas';
|
||||
import { useUi } from '../../stores/useUi'; // Import useUi
|
||||
import { useRouter } from 'vue-router';
|
||||
import TablaPlanillas from '../../components/planillas/tablaPlanillas.vue'; // Corrected path
|
||||
import CardPlanilla from '../../components/planillas/cardPlanilla.vue';
|
||||
|
||||
const planillasStore = usePlanillasStore();
|
||||
const ui = useUi(); // Access the ui store
|
||||
const router = useRouter();
|
||||
|
||||
const isLoading = ref(true); // Set to true initially
|
||||
const errorLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
const currentView = ref('table'); // Default to table view
|
||||
// Initialize currentView from the store's default setting for planillas
|
||||
const currentView = ref(ui.defaultViewPlanillas);
|
||||
|
||||
// Computed property to get planillas from the store
|
||||
const planillasList = computed(() => planillasStore.planillas);
|
||||
@@ -106,12 +119,12 @@ const handleEditPlanilla = (planillaId) => {
|
||||
router.push({ name: 'planillas-edit', params: { id: planillaId } });
|
||||
};
|
||||
|
||||
const btnClass = (view) => {
|
||||
const baseClasses = 'px-4 py-2 rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2';
|
||||
if (currentView.value === view) {
|
||||
return `${baseClasses} text-white shadow-sm view-toggle-active-planillas`;
|
||||
const btnViewClass = (viewType) => {
|
||||
const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out';
|
||||
if (currentView.value === viewType) {
|
||||
return `${base} bg-[var(--accent-color-planillas)] text-white shadow-lg`;
|
||||
}
|
||||
return `${baseClasses} bg-gray-200 text-gray-700 hover:bg-gray-300 focus:ring-gray-400`;
|
||||
return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
203
ui/src/views/planillas/__tests__/PlanillasIndex.spec.js
Normal file
203
ui/src/views/planillas/__tests__/PlanillasIndex.spec.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useUi } from '@/stores/useUi'
|
||||
import { usePlanillasStore } from '@/stores/usePlanillas'
|
||||
import PlanillasIndex from '../PlanillasIndex.vue'
|
||||
import TablaPlanillas from '@/components/planillas/tablaPlanillas.vue'
|
||||
import CardPlanilla from '@/components/planillas/cardPlanilla.vue'
|
||||
|
||||
// Mock child components
|
||||
vi.mock('@/components/planillas/tablaPlanillas.vue', () => ({
|
||||
default: {
|
||||
name: 'TablaPlanillas',
|
||||
props: ['planillas'], // Match actual props
|
||||
emits: ['edit'],
|
||||
template: '<div data-testid="tabla-planillas"></div>',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/components/planillas/cardPlanilla.vue', () => ({
|
||||
default: {
|
||||
name: 'CardPlanilla',
|
||||
props: ['planilla'], // Match actual props
|
||||
emits: ['edit'],
|
||||
template: '<div data-testid="card-planilla"></div>',
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock stores
|
||||
const mockSetDefaultViewPlanillas = vi.fn();
|
||||
const mockFetchPlanillas = vi.fn();
|
||||
|
||||
vi.mock('@/stores/useUi', () => ({
|
||||
useUi: vi.fn(() => ({
|
||||
defaultViewPlanillas: 'table', // Default mock value
|
||||
setDefaultViewPlanillas: mockSetDefaultViewPlanillas,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/usePlanillas', () => ({
|
||||
usePlanillasStore: vi.fn(() => ({
|
||||
planillas: [],
|
||||
fetchPlanillas: mockFetchPlanillas,
|
||||
})),
|
||||
}))
|
||||
|
||||
describe('PlanillasIndex.vue', () => {
|
||||
let uiStoreMock
|
||||
let planillasStoreMock
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
mockFetchPlanillas.mockClear().mockResolvedValue([])
|
||||
mockSetDefaultViewPlanillas.mockClear()
|
||||
|
||||
uiStoreMock = useUi()
|
||||
planillasStoreMock = usePlanillasStore()
|
||||
})
|
||||
|
||||
const mountComponent = () => {
|
||||
return mount(PlanillasIndex, {
|
||||
global: {},
|
||||
})
|
||||
}
|
||||
|
||||
it('fetches planillas on mount', async () => {
|
||||
mountComponent()
|
||||
await mockFetchPlanillas(); // Ensure the promise from fetch resolves
|
||||
expect(mockFetchPlanillas).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('View Rendering based on useUi store', () => {
|
||||
it('renders TablaPlanillas when defaultViewPlanillas is "table"', async () => {
|
||||
uiStoreMock.defaultViewPlanillas = 'table'
|
||||
planillasStoreMock.planillas = [{ id: 1, periodo: '2023-01', total: 5000 }]
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await mockFetchPlanillas();
|
||||
await wrapper.vm.$nextTick() // Wait for reactivity
|
||||
await wrapper.vm.$nextTick() // Additional tick if loading state causes multiple updates
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaPlanillas' }).exists()).toBe(true)
|
||||
expect(wrapper.findComponent({ name: 'TablaPlanillas' }).props('planillas')).toEqual(planillasStoreMock.planillas)
|
||||
expect(wrapper.findComponent({ name: 'CardPlanilla' }).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders CardPlanilla when defaultViewPlanillas is "card"', async () => {
|
||||
uiStoreMock.defaultViewPlanillas = 'card'
|
||||
planillasStoreMock.planillas = [{ id: 1, P1: 'P1' }, { id: 2, P2: 'P2' }]
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await mockFetchPlanillas();
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const cardWrappers = wrapper.findAllComponents({ name: 'CardPlanilla' })
|
||||
expect(cardWrappers.length).toBe(planillasStoreMock.planillas.length)
|
||||
expect(cardWrappers[0].props('planilla')).toEqual(planillasStoreMock.planillas[0])
|
||||
expect(wrapper.findComponent({ name: 'TablaPlanillas' }).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders no data message for table view when no planillas exist', async () => {
|
||||
uiStoreMock.defaultViewPlanillas = 'table';
|
||||
planillasStoreMock.planillas = [];
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchPlanillas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaPlanillas' }).exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('No hay planillas para mostrar');
|
||||
});
|
||||
|
||||
it('renders no data message for card view when no planillas exist', async () => {
|
||||
uiStoreMock.defaultViewPlanillas = 'card';
|
||||
planillasStoreMock.planillas = [];
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchPlanillas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findAllComponents({ name: 'CardPlanilla' }).length).toBe(0);
|
||||
expect(wrapper.text()).toContain('No hay planillas para mostrar');
|
||||
});
|
||||
})
|
||||
|
||||
describe('Local View Toggle Buttons', () => {
|
||||
it('renders toggle buttons and reflects initial view from store (table)', async () => {
|
||||
uiStoreMock.defaultViewPlanillas = 'table';
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchPlanillas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
|
||||
expect(tableViewButton.exists()).toBe(true);
|
||||
expect(cardViewButton.exists()).toBe(true);
|
||||
expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]');
|
||||
expect(cardViewButton.classes()).toContain('bg-gray-200');
|
||||
});
|
||||
|
||||
it('renders toggle buttons and reflects initial view from store (card)', async () => {
|
||||
uiStoreMock.defaultViewPlanillas = 'card';
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchPlanillas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]');
|
||||
expect(tableViewButton.classes()).toContain('bg-gray-200');
|
||||
});
|
||||
|
||||
it('switches to card view on button click and updates button styles, does not call global store action', async () => {
|
||||
uiStoreMock.defaultViewPlanillas = 'table';
|
||||
planillasStoreMock.planillas = [{ id: 1, periodo: 'Test' }];
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchPlanillas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
|
||||
await cardViewButton.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'CardPlanilla' }).exists()).toBe(true);
|
||||
expect(wrapper.findComponent({ name: 'TablaPlanillas' }).exists()).toBe(false);
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]');
|
||||
expect(tableViewButton.classes()).toContain('bg-gray-200');
|
||||
expect(mockSetDefaultViewPlanillas).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('switches back to table view on button click and updates button styles, does not call global store action', async () => {
|
||||
uiStoreMock.defaultViewPlanillas = 'card'; // Start with card view
|
||||
planillasStoreMock.planillas = [{ id: 1, periodo: 'Test' }];
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchPlanillas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]');
|
||||
|
||||
await tableViewButton.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaPlanillas' }).exists()).toBe(true);
|
||||
expect(wrapper.findComponent({ name: 'CardPlanilla' }).exists()).toBe(false);
|
||||
expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]');
|
||||
expect(cardViewButton.classes()).toContain('bg-gray-200');
|
||||
expect(mockSetDefaultViewPlanillas).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -7,13 +7,23 @@
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="mb-6 flex justify-end items-center space-x-3">
|
||||
<span class="text-sm font-medium text-gray-700">Cambiar Vista:</span>
|
||||
<button @click="currentView = 'card'" :class="btnClass('card')">
|
||||
Tarjetas
|
||||
<!-- View Toggle Buttons -->
|
||||
<div class="mb-4 flex justify-end space-x-2">
|
||||
<button
|
||||
@click="currentView = 'table'"
|
||||
:class="btnViewClass('table')"
|
||||
aria-label="Table View"
|
||||
title="Table View"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
||||
</button>
|
||||
<button @click="currentView = 'table'" :class="btnClass('table')">
|
||||
Tabla
|
||||
<button
|
||||
@click="currentView = 'card'"
|
||||
:class="btnViewClass('card')"
|
||||
aria-label="Card View"
|
||||
title="Card View"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -60,18 +70,21 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useTareasStore } from '../../stores/useTareas';
|
||||
import { useUi } from '../../stores/useUi'; // Import useUi
|
||||
import { useRouter } from 'vue-router';
|
||||
import TablaTareas from '../../components/tareas/tablaTareas.vue';
|
||||
import CardTarea from '../../components/tareas/cardTarea.vue';
|
||||
|
||||
const tareasStore = useTareasStore();
|
||||
const ui = useUi(); // Access the ui store
|
||||
const router = useRouter();
|
||||
|
||||
const isLoading = ref(true);
|
||||
const errorLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
const currentView = ref('table'); // Default to table view
|
||||
// Initialize currentView from the store's default setting for tareas
|
||||
const currentView = ref(ui.defaultViewTareas);
|
||||
|
||||
const tareasList = computed(() => tareasStore.tareas);
|
||||
|
||||
@@ -100,12 +113,12 @@ const handleEditTarea = (tareaId) => {
|
||||
router.push({ name: 'tareas-edit', params: { id: tareaId } });
|
||||
};
|
||||
|
||||
const btnClass = (view) => {
|
||||
const baseClasses = 'px-4 py-2 rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2';
|
||||
if (currentView.value === view) {
|
||||
return `${baseClasses} text-white shadow-sm view-toggle-active-tareas`;
|
||||
const btnViewClass = (viewType) => {
|
||||
const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out';
|
||||
if (currentView.value === viewType) {
|
||||
return `${base} bg-[var(--accent-color-tareas)] text-white shadow-lg`;
|
||||
}
|
||||
return `${baseClasses} bg-gray-200 text-gray-700 hover:bg-gray-300 focus:ring-gray-400`;
|
||||
return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
203
ui/src/views/tareas/__tests__/TareasIndex.spec.js
Normal file
203
ui/src/views/tareas/__tests__/TareasIndex.spec.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useUi } from '@/stores/useUi'
|
||||
import { useTareasStore } from '@/stores/useTareas'
|
||||
import TareasIndex from '../TareasIndex.vue'
|
||||
import TablaTareas from '@/components/tareas/tablaTareas.vue'
|
||||
import CardTarea from '@/components/tareas/cardTarea.vue'
|
||||
|
||||
// Mock child components
|
||||
vi.mock('@/components/tareas/tablaTareas.vue', () => ({
|
||||
default: {
|
||||
name: 'TablaTareas',
|
||||
props: ['tareas'], // Match actual props
|
||||
emits: ['edit'],
|
||||
template: '<div data-testid="tabla-tareas"></div>',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/components/tareas/cardTarea.vue', () => ({
|
||||
default: {
|
||||
name: 'CardTarea',
|
||||
props: ['tarea'], // Match actual props
|
||||
emits: ['edit'],
|
||||
template: '<div data-testid="card-tarea"></div>',
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock stores
|
||||
const mockSetDefaultViewTareas = vi.fn();
|
||||
const mockFetchTareas = vi.fn();
|
||||
|
||||
vi.mock('@/stores/useUi', () => ({
|
||||
useUi: vi.fn(() => ({
|
||||
defaultViewTareas: 'table', // Default mock value
|
||||
setDefaultViewTareas: mockSetDefaultViewTareas,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/useTareas', () => ({
|
||||
useTareasStore: vi.fn(() => ({
|
||||
tareas: [],
|
||||
fetchTareas: mockFetchTareas,
|
||||
})),
|
||||
}))
|
||||
|
||||
describe('TareasIndex.vue', () => {
|
||||
let uiStoreMock
|
||||
let tareasStoreMock
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
mockFetchTareas.mockClear().mockResolvedValue([])
|
||||
mockSetDefaultViewTareas.mockClear()
|
||||
|
||||
uiStoreMock = useUi()
|
||||
tareasStoreMock = useTareasStore()
|
||||
})
|
||||
|
||||
const mountComponent = () => {
|
||||
return mount(TareasIndex, {
|
||||
global: {},
|
||||
})
|
||||
}
|
||||
|
||||
it('fetches tareas on mount', async () => {
|
||||
mountComponent()
|
||||
await mockFetchTareas(); // Ensure the promise from fetch resolves
|
||||
expect(mockFetchTareas).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('View Rendering based on useUi store', () => {
|
||||
it('renders TablaTareas when defaultViewTareas is "table"', async () => {
|
||||
uiStoreMock.defaultViewTareas = 'table'
|
||||
tareasStoreMock.tareas = [{ id: 1, titulo: 'Test Task', completada: false }]
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await mockFetchTareas();
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaTareas' }).exists()).toBe(true)
|
||||
expect(wrapper.findComponent({ name: 'TablaTareas' }).props('tareas')).toEqual(tareasStoreMock.tareas)
|
||||
expect(wrapper.findComponent({ name: 'CardTarea' }).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders CardTarea when defaultViewTareas is "card"', async () => {
|
||||
uiStoreMock.defaultViewTareas = 'card'
|
||||
tareasStoreMock.tareas = [{ id: 1, T1: 'T1' }, { id: 2, T2: 'T2' }]
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await mockFetchTareas();
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const cardWrappers = wrapper.findAllComponents({ name: 'CardTarea' })
|
||||
expect(cardWrappers.length).toBe(tareasStoreMock.tareas.length)
|
||||
expect(cardWrappers[0].props('tarea')).toEqual(tareasStoreMock.tareas[0])
|
||||
expect(wrapper.findComponent({ name: 'TablaTareas' }).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders no data message for table view when no tareas exist', async () => {
|
||||
uiStoreMock.defaultViewTareas = 'table';
|
||||
tareasStoreMock.tareas = [];
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchTareas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaTareas' }).exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('No hay tareas para mostrar');
|
||||
});
|
||||
|
||||
it('renders no data message for card view when no tareas exist', async () => {
|
||||
uiStoreMock.defaultViewTareas = 'card';
|
||||
tareasStoreMock.tareas = [];
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchTareas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findAllComponents({ name: 'CardTarea' }).length).toBe(0);
|
||||
expect(wrapper.text()).toContain('No hay tareas para mostrar');
|
||||
});
|
||||
})
|
||||
|
||||
describe('Local View Toggle Buttons', () => {
|
||||
it('renders toggle buttons and reflects initial view from store (table)', async () => {
|
||||
uiStoreMock.defaultViewTareas = 'table';
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchTareas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
|
||||
expect(tableViewButton.exists()).toBe(true);
|
||||
expect(cardViewButton.exists()).toBe(true);
|
||||
expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]');
|
||||
expect(cardViewButton.classes()).toContain('bg-gray-200');
|
||||
});
|
||||
|
||||
it('renders toggle buttons and reflects initial view from store (card)', async () => {
|
||||
uiStoreMock.defaultViewTareas = 'card';
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchTareas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]');
|
||||
expect(tableViewButton.classes()).toContain('bg-gray-200');
|
||||
});
|
||||
|
||||
it('switches to card view on button click and updates button styles, does not call global store action', async () => {
|
||||
uiStoreMock.defaultViewTareas = 'table';
|
||||
tareasStoreMock.tareas = [{ id: 1, titulo: 'Test' }];
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchTareas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
|
||||
await cardViewButton.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'CardTarea' }).exists()).toBe(true);
|
||||
expect(wrapper.findComponent({ name: 'TablaTareas' }).exists()).toBe(false);
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]');
|
||||
expect(tableViewButton.classes()).toContain('bg-gray-200');
|
||||
expect(mockSetDefaultViewTareas).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('switches back to table view on button click and updates button styles, does not call global store action', async () => {
|
||||
uiStoreMock.defaultViewTareas = 'card'; // Start with card view
|
||||
tareasStoreMock.tareas = [{ id: 1, titulo: 'Test' }];
|
||||
const wrapper = mountComponent();
|
||||
await mockFetchTareas();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const cardViewButton = wrapper.find('button[aria-label="Card View"]');
|
||||
const tableViewButton = wrapper.find('button[aria-label="Table View"]');
|
||||
expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]');
|
||||
|
||||
await tableViewButton.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TablaTareas' }).exists()).toBe(true);
|
||||
expect(wrapper.findComponent({ name: 'CardTarea' }).exists()).toBe(false);
|
||||
expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]');
|
||||
expect(cardViewButton.classes()).toContain('bg-gray-200');
|
||||
expect(mockSetDefaultViewTareas).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
})
|
||||
Reference in New Issue
Block a user