Merge branch 'dev' into 7119-showCountryName
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details

This commit is contained in:
Alex Moreno 2025-02-07 06:00:54 +00:00
commit fc44424dda
3 changed files with 528 additions and 68 deletions

View File

@ -41,7 +41,6 @@ const filteredItems = computed(() => {
return locale.includes(normalizedSearch);
});
});
const filteredPinnedModules = computed(() => {
if (!search.value) return pinnedModules.value;
const normalizedSearch = search.value
@ -72,7 +71,7 @@ watch(
items.value = [];
getRoutes();
},
{ deep: true }
{ deep: true },
);
function findMatches(search, item) {
@ -104,33 +103,40 @@ function addChildren(module, route, parent) {
}
function getRoutes() {
if (props.source === 'main') {
const modules = Object.assign([], navigation.getModules().value);
for (const item of modules) {
const moduleDef = routes.find(
(route) => toLowerCamel(route.name) === item.module
);
if (!moduleDef) continue;
item.children = [];
addChildren(item.module, moduleDef, item.children);
}
items.value = modules;
const handleRoutes = {
main: getMainRoutes,
card: getCardRoutes,
};
try {
handleRoutes[props.source]();
} catch (error) {
throw new Error(`Method is not defined`);
}
}
function getMainRoutes() {
const modules = Object.assign([], navigation.getModules().value);
if (props.source === 'card') {
const currentRoute = route.matched[1];
const currentModule = toLowerCamel(currentRoute.name);
let moduleDef = routes.find(
(route) => toLowerCamel(route.name) === currentModule
for (const item of modules) {
const moduleDef = routes.find(
(route) => toLowerCamel(route.name) === item.module,
);
if (!moduleDef) continue;
item.children = [];
if (!moduleDef) return;
if (!moduleDef?.menus) moduleDef = betaGetRoutes();
addChildren(currentModule, moduleDef, items.value);
addChildren(item.module, moduleDef, item.children);
}
items.value = modules;
}
function getCardRoutes() {
const currentRoute = route.matched[1];
const currentModule = toLowerCamel(currentRoute.name);
let moduleDef = routes.find((route) => toLowerCamel(route.name) === currentModule);
if (!moduleDef) return;
if (!moduleDef?.menus) moduleDef = betaGetRoutes();
addChildren(currentModule, moduleDef, items.value);
}
function betaGetRoutes() {
@ -223,9 +229,16 @@ const searchModule = () => {
</template>
<template v-for="(item, index) in filteredItems" :key="item.name">
<template
v-if="search ||item.children && !filteredPinnedModules.has(item.name)"
v-if="
search ||
(item.children && !filteredPinnedModules.has(item.name))
"
>
<LeftMenuItem :item="item" group="modules" :class="search && index === 0 ? 'searched' : ''">
<LeftMenuItem
:item="item"
group="modules"
:class="search && index === 0 ? 'searched' : ''"
>
<template #side>
<QBtn
v-if="item.isPinned === true"
@ -342,7 +355,7 @@ const searchModule = () => {
.header {
color: var(--vn-label-color);
}
.searched{
.searched {
background-color: var(--vn-section-hover-color);
}
</style>

View File

@ -1,9 +1,12 @@
import { vi, describe, expect, it, beforeAll } from 'vitest';
import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import Leftmenu from 'components/LeftMenu.vue';
import * as vueRouter from 'vue-router';
import { useNavigationStore } from 'src/stores/useNavigationStore';
let vm;
let navigation;
vi.mock('src/router/modules', () => ({
default: [
{
@ -21,6 +24,16 @@ vi.mock('src/router/modules', () => ({
{
path: '',
name: 'CustomerMain',
meta: {
menu: 'Customer',
menuChildren: [
{
name: 'CustomerCreditContracts',
title: 'creditContracts',
icon: 'vn:solunion',
},
],
},
children: [
{
path: 'list',
@ -28,6 +41,13 @@ vi.mock('src/router/modules', () => ({
meta: {
title: 'list',
icon: 'view_list',
menuChildren: [
{
name: 'CustomerCreditContracts',
title: 'creditContracts',
icon: 'vn:solunion',
},
],
},
},
{
@ -44,51 +64,325 @@ vi.mock('src/router/modules', () => ({
},
],
}));
describe('Leftmenu', () => {
let vm;
let navigation;
beforeAll(() => {
vi.spyOn(axios, 'get').mockResolvedValue({
data: [],
});
vm = createWrapper(Leftmenu, {
propsData: {
source: 'main',
vi.spyOn(vueRouter, 'useRoute').mockReturnValue({
matched: [
{
path: '/',
redirect: {
name: 'Dashboard',
},
}).vm;
navigation = useNavigationStore();
navigation.fetchPinned = vi.fn().mockReturnValue(Promise.resolve(true));
navigation.getModules = vi.fn().mockReturnValue({
value: [
name: 'Main',
meta: {},
props: {
default: false,
},
children: [
{
name: 'customer',
title: 'customer.pageTitles.customers',
icon: 'vn:customer',
module: 'customer',
path: '/dashboard',
name: 'Dashboard',
meta: {
title: 'dashboard',
icon: 'dashboard',
},
},
],
},
{
path: '/customer',
redirect: {
name: 'CustomerMain',
},
name: 'Customer',
meta: {
title: 'customers',
icon: 'vn:client',
moduleName: 'Customer',
keyBinding: 'c',
menu: 'customer',
},
},
],
query: {},
params: {},
meta: { moduleName: 'mockName' },
path: 'mockName/1',
name: 'Customer',
});
function mount(source = 'main') {
vi.spyOn(axios, 'get').mockResolvedValue({
data: [],
});
const wrapper = createWrapper(Leftmenu, {
propsData: {
source,
},
});
navigation = useNavigationStore();
navigation.fetchPinned = vi.fn().mockReturnValue(Promise.resolve(true));
navigation.getModules = vi.fn().mockReturnValue({
value: [
{
name: 'customer',
title: 'customer.pageTitles.customers',
icon: 'vn:customer',
module: 'customer',
},
],
});
return wrapper;
}
describe('getRoutes', () => {
afterEach(() => vi.clearAllMocks());
const getRoutes = vi.fn().mockImplementation((props, getMethodA, getMethodB) => {
const handleRoutes = {
methodA: getMethodA,
methodB: getMethodB,
};
try {
handleRoutes[props.source]();
} catch (error) {
throw Error('Method not defined');
}
});
const getMethodA = vi.fn();
const getMethodB = vi.fn();
const fn = (props) => getRoutes(props, getMethodA, getMethodB);
it('should call getMethodB when source is card', () => {
let props = { source: 'methodB' };
fn(props);
expect(getMethodB).toHaveBeenCalled();
expect(getMethodA).not.toHaveBeenCalled();
});
it('should call getMethodA when source is main', () => {
let props = { source: 'methodA' };
fn(props);
expect(getMethodA).toHaveBeenCalled();
expect(getMethodB).not.toHaveBeenCalled();
});
it('should call getMethodA when source is not exists or undefined', () => {
let props = { source: 'methodC' };
expect(() => fn(props)).toThrowError('Method not defined');
expect(getMethodA).not.toHaveBeenCalled();
expect(getMethodB).not.toHaveBeenCalled();
});
});
describe('Leftmenu as card', () => {
beforeAll(() => {
vm = mount('card').vm;
});
it('should get routes for card source', async () => {
vm.getRoutes();
});
});
describe('Leftmenu as main', () => {
beforeEach(() => {
vm = mount().vm;
});
it('should initialize with default props', () => {
expect(vm.source).toBe('main');
});
it('should filter items based on search input', async () => {
vm.search = 'cust';
await vm.$nextTick();
expect(vm.filteredItems[0].name).toEqual('customer');
expect(vm.filteredItems[0].module).toEqual('customer');
});
it('should filter items based on search input', async () => {
vm.search = 'Rou';
await vm.$nextTick();
expect(vm.filteredItems).toEqual([]);
});
it('should return pinned items', () => {
vm.items = [
{ name: 'Item 1', isPinned: false },
{ name: 'Item 2', isPinned: true },
];
expect(vm.pinnedModules).toEqual(
new Map([['Item 2', { name: 'Item 2', isPinned: true }]]),
);
});
it('should find matches in routes', () => {
const search = 'child1';
const item = {
children: [
{ name: 'child1', children: [] },
{ name: 'child2', children: [] },
],
};
const matches = vm.findMatches(search, item);
expect(matches).toEqual([{ name: 'child1', children: [] }]);
});
it('should not proceed if event is already prevented', async () => {
const item = { module: 'testModule', isPinned: false };
const event = {
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
defaultPrevented: true,
};
await vm.togglePinned(item, event);
expect(event.preventDefault).not.toHaveBeenCalled();
expect(event.stopPropagation).not.toHaveBeenCalled();
});
it('should call quasar.notify with success message', async () => {
const item = { module: 'testModule', isPinned: false };
const event = {
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
defaultPrevented: false,
};
const response = { data: { id: 1 } };
vi.spyOn(axios, 'post').mockResolvedValue(response);
vi.spyOn(vm.quasar, 'notify');
await vm.togglePinned(item, event);
expect(vm.quasar.notify).toHaveBeenCalledWith({
message: 'Data saved',
type: 'positive',
});
});
it('should return a proper formated object with two child items', async () => {
const expectedMenuItem = [
{
children: null,
name: 'CustomerList',
title: 'globals.pageTitles.list',
icon: 'view_list',
},
{
children: null,
name: 'CustomerCreate',
title: 'globals.pageTitles.createCustomer',
icon: 'vn:addperson',
},
];
const firstMenuItem = vm.items[0];
expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem));
it('should handle a single matched route with a menu', () => {
const route = {
matched: [{ meta: { menu: 'customer' } }],
};
const result = vm.betaGetRoutes();
expect(result.meta.menu).toEqual(route.matched[0].meta.menu);
});
it('should get routes for main source', () => {
vm.props.source = 'main';
vm.getRoutes();
expect(navigation.getModules).toHaveBeenCalled();
});
it('should find direct child matches', () => {
const search = 'child1';
const item = {
children: [{ name: 'child1' }, { name: 'child2' }],
};
const result = vm.findMatches(search, item);
expect(result).toEqual([{ name: 'child1' }]);
});
it('should find nested child matches', () => {
const search = 'child3';
const item = {
children: [
{ name: 'child1' },
{
name: 'child2',
children: [{ name: 'child3' }],
},
],
};
const result = vm.findMatches(search, item);
expect(result).toEqual([{ name: 'child3' }]);
});
});
describe('normalize', () => {
beforeAll(() => {
vm = mount('card').vm;
});
it('should normalize and lowercase text', () => {
const input = 'ÁÉÍÓÚáéíóú';
const expected = 'aeiouaeiou';
expect(vm.normalize(input)).toBe(expected);
});
it('should handle empty string', () => {
const input = '';
const expected = '';
expect(vm.normalize(input)).toBe(expected);
});
it('should handle text without diacritics', () => {
const input = 'hello';
const expected = 'hello';
expect(vm.normalize(input)).toBe(expected);
});
it('should handle mixed text', () => {
const input = 'Héllo Wórld!';
const expected = 'hello world!';
expect(vm.normalize(input)).toBe(expected);
});
});
describe('addChildren', () => {
const module = 'testModule';
beforeEach(() => {
vm = mount().vm;
vi.clearAllMocks();
});
it('should add menu items to parent if matches are found', () => {
const parent = 'testParent';
const route = {
meta: {
menu: 'testMenu',
},
children: [{ name: 'child1' }, { name: 'child2' }],
};
vm.addChildren(module, route, parent);
expect(navigation.addMenuItem).toHaveBeenCalled();
});
it('should handle routes with no meta menu', () => {
const route = {
meta: {},
menus: {},
};
const parent = [];
vm.addChildren(module, route, parent);
expect(navigation.addMenuItem).toHaveBeenCalled();
});
it('should handle empty parent array', () => {
const parent = [];
const route = {
meta: {
menu: 'child11',
},
children: [
{
name: 'child1',
meta: {
menuChildren: [
{
name: 'CustomerCreditContracts',
title: 'creditContracts',
icon: 'vn:solunion',
},
],
},
},
],
};
vm.addChildren(module, route, parent);
expect(navigation.addMenuItem).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,153 @@
import { setActivePinia, createPinia } from 'pinia';
import { describe, beforeEach, afterEach, it, expect, vi, beforeAll } from 'vitest';
import { useNavigationStore } from '../useNavigationStore';
import axios from 'axios';
let store;
vi.mock('src/router/modules', () => [
{ name: 'Item', meta: {} },
{ name: 'Shelving', meta: {} },
{ name: 'Order', meta: {} },
]);
vi.mock('src/filters', () => ({
toLowerCamel: vi.fn((name) => name.toLowerCase()),
}));
const modulesMock = [
{
name: 'Item',
children: null,
title: 'globals.pageTitles.undefined',
icon: undefined,
module: 'item',
isPinned: true,
},
{
name: 'Shelving',
children: null,
title: 'globals.pageTitles.undefined',
icon: undefined,
module: 'shelving',
isPinned: false,
},
{
name: 'Order',
children: null,
title: 'globals.pageTitles.undefined',
icon: undefined,
module: 'order',
isPinned: false,
},
];
const pinnedModulesMock = [
{
name: 'Item',
children: null,
title: 'globals.pageTitles.undefined',
icon: undefined,
module: 'item',
isPinned: true,
},
];
describe('useNavigationStore', () => {
beforeEach(() => {
setActivePinia(createPinia());
vi.spyOn(axios, 'get').mockResolvedValue({ data: true });
store = useNavigationStore();
store.getModules = vi.fn().mockReturnValue({
value: modulesMock,
});
store.getPinnedModules = vi.fn().mockReturnValue({
value: pinnedModulesMock,
});
});
afterEach(() => {
vi.clearAllMocks();
});
it('should return modules with correct structure', () => {
const store = useNavigationStore();
const modules = store.getModules();
expect(modules.value).toEqual(modulesMock);
});
it('should return pinned modules', () => {
const store = useNavigationStore();
const pinnedModules = store.getPinnedModules();
expect(pinnedModules.value).toEqual(pinnedModulesMock);
});
it('should toggle pinned modules', () => {
const store = useNavigationStore();
store.togglePinned('item');
store.togglePinned('shelving');
expect(store.pinnedModules).toEqual(['item', 'shelving']);
store.togglePinned('item');
expect(store.pinnedModules).toEqual(['shelving']);
});
it('should fetch pinned modules', async () => {
vi.spyOn(axios, 'get').mockResolvedValue({
data: [{ id: 1, workerFk: 9, moduleFk: 'order', position: 1 }],
});
const store = useNavigationStore();
await store.fetchPinned();
expect(store.pinnedModules).toEqual(['order']);
});
it('should add menu item correctly', () => {
const store = useNavigationStore();
const module = 'customer';
const parent = [];
const route = {
name: 'customer',
title: 'Customer',
icon: 'customer',
meta: {
keyBinding: 'ctrl+shift+c',
name: 'customer',
title: 'Customer',
icon: 'customer',
menu: 'customer',
menuChildren: [{ name: 'customer', title: 'Customer', icon: 'customer' }],
},
};
const result = store.addMenuItem(module, route, parent);
const expectedItem = {
children: [
{
icon: 'customer',
name: 'customer',
title: 'globals.pageTitles.Customer',
},
],
icon: 'customer',
keyBinding: 'ctrl+shift+c',
name: 'customer',
title: 'globals.pageTitles.Customer',
};
expect(result).toEqual(expectedItem);
expect(parent.length).toBe(1);
expect(parent).toEqual([expectedItem]);
});
it('should not add menu item if condition is not met', () => {
const store = useNavigationStore();
const module = 'testModule';
const route = { meta: { hidden: true, menuchildren: {} } };
const parent = [];
const result = store.addMenuItem(module, route, parent);
expect(result).toBeUndefined();
expect(parent.length).toBe(0);
});
});