Merge branch 'main' into feature/appearance-settings
This commit is contained in:
@@ -14,7 +14,8 @@
|
||||
"axios": "^1.9.0",
|
||||
"pinia": "^3.0.2",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.1"
|
||||
"vue-router": "^4.5.1",
|
||||
"@empresa/prisma-schema": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.2",
|
||||
|
||||
@@ -43,20 +43,7 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// Define the structure of the employee object based on the Prisma schema
|
||||
interface Employee {
|
||||
id: string | number // Changed from BigInt to string | number for easier handling in frontend
|
||||
name: string
|
||||
cedula: number // Changed from BigInt
|
||||
avatar_url?: string
|
||||
telefono?: string
|
||||
ubicacion: string
|
||||
idciat?: string
|
||||
grupo_estudio?: string
|
||||
// created_at and updated_at are usually not displayed directly in a summary card
|
||||
// empleado: boolean // This is implicit as it's an employee card
|
||||
}
|
||||
import type { Employee } from '@empresa/prisma-schema'
|
||||
|
||||
const props = defineProps({
|
||||
employee: {
|
||||
|
||||
@@ -52,6 +52,10 @@ describe('useUi Store', () => {
|
||||
})
|
||||
|
||||
it('loads settings from localStorage including new accent colors if present', () => {
|
||||
expect(localStorageMock.getItem).toHaveBeenCalledWith(APPEARANCE_STORAGE_KEY)
|
||||
})
|
||||
|
||||
it('loads settings from localStorage if present', () => {
|
||||
const storedSettings = {
|
||||
primaryColor: '#FF0000',
|
||||
theme: 'dark',
|
||||
@@ -126,6 +130,9 @@ describe('useUi Store', () => {
|
||||
'primaryColor', 'secondaryColor', 'warningColor', 'fontFamily',
|
||||
'fontSize', 'animationsEnabled', 'backgroundColor', 'theme',
|
||||
'accentColorEmpleados', 'accentColorTareas', 'accentColorPlanillas', 'accentColorAsistencias',
|
||||
const appearanceSettingKeys = [
|
||||
'primaryColor', 'secondaryColor', 'warningColor', 'fontFamily',
|
||||
'fontSize', 'animationsEnabled', 'backgroundColor', 'theme',
|
||||
]
|
||||
|
||||
it('setPrimaryColor updates state and saves to localStorage', () => {
|
||||
@@ -252,5 +259,11 @@ 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -46,6 +46,47 @@ describe('SettingsView.vue', () => {
|
||||
|
||||
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({
|
||||
primaryColor: '#111111',
|
||||
secondaryColor: '#222222',
|
||||
warningColor: '#333333',
|
||||
@@ -62,6 +103,8 @@ describe('SettingsView.vue', () => {
|
||||
})
|
||||
|
||||
// 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#warningColor').element.value).toBe('#333333')
|
||||
@@ -97,11 +140,32 @@ describe('SettingsView.vue', () => {
|
||||
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')
|
||||
})
|
||||
|
||||
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')
|
||||
@@ -114,6 +178,12 @@ describe('SettingsView.vue', () => {
|
||||
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)
|
||||
})
|
||||
@@ -168,6 +238,19 @@ describe('SettingsView.vue', () => {
|
||||
|
||||
// Test primaryColor
|
||||
store.primaryColor = '#001122' // Directly manipulate the store used by the component
|
||||
=======
|
||||
const wrapper = createWrapper()
|
||||
const spy = vi.spyOn(store, 'setTheme')
|
||||
const select = wrapper.find('select#theme')
|
||||
await select.setValue('dark') // .setValue on select triggers 'change'
|
||||
expect(spy).toHaveBeenCalledWith('dark')
|
||||
})
|
||||
|
||||
it('updates input values when store state changes programmatically', async () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
// 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')
|
||||
|
||||
@@ -203,6 +286,7 @@ describe('SettingsView.vue', () => {
|
||||
vi.useFakeTimers()
|
||||
|
||||
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)
|
||||
|
||||
23
ui/tsconfig.json
Normal file
23
ui/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "preserve",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true, // Vite handles emission
|
||||
// Add Vue-specific options if not in base
|
||||
"allowJs": true, // If you have JS files too
|
||||
// Vite projects often use this for path aliases like @/
|
||||
// The base already has baseUrl: "."
|
||||
// Paths for @empresa/prisma-schema are inherited from tsconfig.base.json
|
||||
"paths": {
|
||||
"@/*": ["src/*"] // Local alias for UI project
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }] // Common for Vite projects
|
||||
}
|
||||
12
ui/tsconfig.node.json
Normal file
12
ui/tsconfig.node.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"target": "esnext",
|
||||
// Specific overrides for Node context if needed
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.js"] // Or .ts if you use TypeScript for Vite config
|
||||
}
|
||||
Reference in New Issue
Block a user