Merge branch 'main' into feature/appearance-settings

This commit is contained in:
josedario87
2025-05-30 18:22:59 -06:00
committed by GitHub
28 changed files with 478 additions and 243 deletions

View File

@@ -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",

View File

@@ -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: {

View File

@@ -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)
})
})
})
})

View File

@@ -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
View 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
View 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
}