ref #6104 recreate vnlog #96

Merged
jorgep merged 24 commits from 6104-changeVnLog into dev 2023-12-11 11:34:36 +00:00
19 changed files with 1349 additions and 199 deletions

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "salix-front",
"version": "23.40.01",
"version": "23.36.01",
"lockfileVersion": 3,
"requires": true,
"packages": {

View File

@ -9,7 +9,7 @@
"lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test:e2e": "cypress open",
"test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run --browser chromium",
"test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run",
jorgep marked this conversation as resolved Outdated
Outdated
Review

como mucho quita lo del browser i que use electron que es el de por defecto, pero gulp si q esta bien que lo haga

como mucho quita lo del browser i que use electron que es el de por defecto, pero gulp si q esta bien que lo haga
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit": "vitest",
"test:unit:ci": "vitest run"

View File

@ -29,7 +29,7 @@ module.exports = configure(function (/* ctx */) {
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli/boot-files
boot: ['i18n', 'axios', 'vnDate'],
boot: ['i18n', 'axios', 'vnDate', 'validations'],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ['app.scss'],

6
src/boot/validations.js Normal file
View File

@ -0,0 +1,6 @@
import { boot } from 'quasar/wrappers';
import { useValidationsStore } from 'src/stores/useValidationsStore';
export default boot(async ({ store }) => {
await useValidationsStore(store).fetchModels();
});

View File

@ -46,7 +46,7 @@ async function fetch() {
if ($props.limit) filter.limit = $props.limit;
const { data } = await axios.get($props.url, {
params: { filter },
params: { filter: JSON.stringify(filter) },
});
emit('onFetch', data);

View File

@ -0,0 +1,88 @@
<script setup>
import { watch } from 'vue';
import { toDateString } from 'src/filters';
const props = defineProps({
value: { type: [String, Number, Boolean, Object], default: undefined },
});
const maxStrLen = 512;
let t = '';
let cssClass = '';
let type;
const updateValue = () => {
type = typeof props.value;
if (props.value == null) {
t = '∅';
cssClass = 'json-null';
} else {
cssClass = `json-${type}`;
switch (type) {
case 'number':
if (Number.isInteger(props.value)) {
t = props.value.toString();
} else {
t = (
Math.round((props.value + Number.EPSILON) * 1000) / 1000
).toString();
}
break;
case 'boolean':
t = props.value ? '✓' : '✗';
cssClass = `json-${props.value ? 'true' : 'false'}`;
break;
case 'string':
t =
props.value.length <= maxStrLen
? props.value
: props.value.substring(0, maxStrLen) + '...';
break;
case 'object':
if (props.value instanceof Date) {
t = toDateString(props.value);
} else {
t = props.value.toString();
}
break;
default:
t = props.value.toString();
}
}
};
watch(() => props.value, updateValue);
updateValue();
</script>
<template>
<span
:title="type === 'string' && props.value.length > maxStrLen ? props.value : ''"
:class="{ [cssClass]: t !== '' }"
>
{{ t }}
</span>
</template>
<style scoped>
.json-string {
color: #d172cc;
}
.json-object {
color: #d1a572;
}
.json-number {
color: #85d0ff;
}
.json-true {
color: #7dc489;
}
.json-false {
color: #c74949;
}
.json-null {
color: #cd7c7c;
font-style: italic;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
export function djb2a(string) {
let hash = 5381;
for (let i = 0; i < string.length; i++)
hash = ((hash << 5) + hash) ^ string.charCodeAt(i);
return hash >>> 0;
}
export function useColor(value) {
return '#' + colors[djb2a(value || '') % colors.length];
}
const colors = [
'b5b941', // Yellow
'ae9681', // Peach
'd78767', // Salmon
'cc7000', // Orange bright
'e2553d', // Coral
'8B0000', // Red dark
'de4362', // Red crimson
'FF1493', // Ping intense
'be39a2', // Pink light
'b754cf', // Purple middle
'a87ba8', // Pink
'8a69cd', // Blue lavender
'ab20ab', // Purple dark
'00b5b8', // Turquoise
'1fa8a1', // Green ocean
'5681cf', // Blue steel
'3399fe', // Blue sky
'6d9c3e', // Green chartreuse
'51bb51', // Green lime
'518b8b', // Gray board
'7e7e7e', // Gray
'5d5d5d', // Gray dark
];

View File

@ -1,19 +1,12 @@
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import validator from 'validator';
const models = ref(null);
import { useValidationsStore } from 'src/stores/useValidationsStore';
export function useValidator() {
if (!models.value) fetch();
function fetch() {
axios.get('Schemas/ModelInfo').then((response) => (models.value = response.data));
}
const models = useValidationsStore().validations;
function validate(propertyRule) {
const modelInfo = models.value;
const modelInfo = models;
if (!modelInfo || !propertyRule) return;
const rule = propertyRule.split('.');
@ -75,5 +68,6 @@ export function useValidator() {
return {
validate,
models,
};
}

View File

@ -2,6 +2,7 @@ import toLowerCase from './toLowerCase';
import toDate from './toDate';
import toDateString from './toDateString';
import toDateHour from './toDateHour';
import toRelativeDate from './toRelativeDate';
import toCurrency from './toCurrency';
import toPercentage from './toPercentage';
import toLowerCamel from './toLowerCamel';
@ -13,6 +14,7 @@ export {
toDate,
toDateString,
toDateHour,
toRelativeDate,
toCurrency,
toPercentage,
dashIfEmpty,

View File

@ -0,0 +1,32 @@
import { useI18n } from 'vue-i18n';
export default function formatDate(dateVal) {
const { t } = useI18n();
const today = new Date();
if (dateVal == null) return '';
const date = new Date(dateVal);
const dateZeroTime = new Date(dateVal);
dateZeroTime.setHours(0, 0, 0, 0);
const diff = Math.trunc(
(today.getTime() - dateZeroTime.getTime()) / (1000 * 3600 * 24)
);
let format;
if (diff === 0) format = t('globals.today');
else if (diff === 1) format = t('globals.yesterday');
else if (diff > 1 && diff < 7) {
const options = { weekday: 'short' };
format = date.toLocaleDateString(t('globals.dateFormat'), options);
} else if (today.getFullYear() === date.getFullYear()) {
const options = { day: 'numeric', month: 'short' };
format = date.toLocaleDateString(t('globals.dateFormat'), options);
} else {
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
format = date.toLocaleDateString(t('globals.dateFormat'), options);
}
// Formatear la hora en HH:mm
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${format} ${hours}:${minutes}`;
}

View File

@ -5,6 +5,9 @@ export default {
en: 'English',
},
language: 'Language',
entity: 'Entity',
user: 'User',
details: 'Details',
collapseMenu: 'Collapse left menu',
backToDashboard: 'Return to dashboard',
notifications: 'Notifications',
@ -13,8 +16,11 @@ export default {
pinnedModules: 'Pinned modules',
darkMode: 'Dark mode',
logOut: 'Log out',
date: 'Date',
dataSaved: 'Data saved',
dataDeleted: 'Data deleted',
search: 'Search',
changes: 'Changes',
add: 'Add',
create: 'Create',
save: 'Save',
@ -36,6 +42,9 @@ export default {
summary: {
basicData: 'Basic data',
},
today: 'Today',
yesterday: 'Yesterday',
dateFormat: 'en-GB',
microsip: 'Open in MicroSIP',
noSelectedRows: `You don't have any line selected`,
},

View File

@ -5,6 +5,9 @@ export default {
en: 'Inglés',
},
language: 'Idioma',
entity: 'Entidad',
user: 'Usuario',
details: 'Detalles',
collapseMenu: 'Contraer menú lateral',
backToDashboard: 'Volver al tablón',
notifications: 'Notificaciones',
@ -13,8 +16,11 @@ export default {
pinnedModules: 'Módulos fijados',
darkMode: 'Modo oscuro',
logOut: 'Cerrar sesión',
date: 'Fecha',
dataSaved: 'Datos guardados',
dataDeleted: 'Datos eliminados',
search: 'Buscar',
changes: 'Cambios',
add: 'Añadir',
create: 'Crear',
save: 'Guardar',
@ -36,6 +42,9 @@ export default {
summary: {
basicData: 'Datos básicos',
},
today: 'Hoy',
yesterday: 'Ayer',
dateFormat: 'es-ES',
noSelectedRows: `No tienes ninguna línea seleccionada`,
microsip: 'Abrir en MicroSIP',
},

View File

@ -0,0 +1,18 @@
import axios from 'axios';
import { defineStore } from 'pinia';
export const useValidationsStore = defineStore('validationsStore', {
state: () => ({
validations: null,
}),
actions: {
async fetchModels() {
try {
const { data } = await axios.get('Schemas/modelinfo');
this.validations = data;
} catch (error) {
console.error('Error al obtener las validaciones:', error);
}
},
},
});

View File

@ -0,0 +1,25 @@
/// <reference types="cypress" />
jorgep marked this conversation as resolved
Review

Los he probado y fallan

Los he probado y fallan
Review

A mi me funcionan, mirémoslo en mi ordenador a ver @alexm

A mi me funcionan, mirémoslo en mi ordenador a ver @alexm
Review

Lo miramos en mi ordenador y si que funcionaba

Lo miramos en mi ordenador y si que funcionaba
describe('VnLog', () => {
const chips = [
':nth-child(1) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content',
':nth-child(2) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content',
];
beforeEach(() => {
cy.login('developer');
cy.visit(`/#/claim/${1}/log`);
cy.openRightMenu('.timeline');
jorgep marked this conversation as resolved Outdated
Outdated
Review

Mira si pots gastar les funcions (commands.js) de cypress o si et fa falta alguno creartelo

Mira si pots gastar les funcions (commands.js) de cypress o si et fa falta alguno creartelo
});
it('should filter by insert actions', () => {
cy.checkOption(':nth-child(7) > .q-checkbox');
cy.get('.q-page').click();
cy.validateContent(chips[0], 'Document');
cy.validateContent(chips[1], 'Beginning');
});
it('should filter by entity', () => {
cy.selectOption('.q-drawer--right .q-item > .q-select', 'Claim');
cy.get('.q-page').click();
cy.validateContent(chips[0], 'Claim');
});
});

View File

@ -1,4 +1,6 @@
describe('WorkerList', () => {
const workerFieldNames =
'.card-list-body > .list-items > :nth-child(2) > .value > span';
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
@ -6,20 +8,14 @@ describe('WorkerList', () => {
});
jorgep marked this conversation as resolved
Review

Mira si pots simplificar els e2e gastant comandos de cypress test/cypress/support/commands.js
Exemple: test/cypress/integration/claimDevelopment.spec.js

Mira si pots simplificar els e2e gastant comandos de cypress test/cypress/support/commands.js Exemple: test/cypress/integration/claimDevelopment.spec.js
it('should load workers', () => {
cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span')
.eq(0)
.should('have.text', 'JessicaJones');
cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span')
.eq(1)
.should('have.text', 'BruceBanner');
cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span')
.eq(2)
.should('have.text', 'CharlesXavier');
cy.get(workerFieldNames).eq(0).should('have.text', 'JessicaJones');
cy.get(workerFieldNames).eq(1).should('have.text', 'BruceBanner');
cy.get(workerFieldNames).eq(2).should('have.text', 'CharlesXavier');
});
it('should open the worker summary', () => {
cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(1).click();
cy.get('.summaryHeader div').should('have.text', '1109 - Bruce Banner');
cy.openListSummary(0);
cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones');
cy.get('.summary .header').eq(0).invoke('text').should('include', 'Basic data');
cy.get('.summary .header').eq(1).should('have.text', 'User data');
});

View File

@ -51,15 +51,14 @@ Cypress.Commands.add('getValue', (selector) => {
return cy.get(selector + '.q-checkbox__inner');
}
// Si es un QSelect
else if ($el.find('.q-select__dropdown-icon').length) {
if ($el.find('.q-select__dropdown-icon').length) {
return cy.get(
selector +
'> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input'
);
} else {
// Puedes añadir un log o lanzar un error si el elemento no es reconocido
cy.log('Elemento no soportado');
}
// Puedes añadir un log o lanzar un error si el elemento no es reconocido
cy.log('Elemento no soportado');
});
});
@ -70,7 +69,7 @@ Cypress.Commands.add('selectOption', (selector, option) => {
});
Cypress.Commands.add('checkOption', (selector) => {
cy.wrap(selector).find('.q-checkbox__inner').click();
cy.get(selector).find('.q-checkbox__inner').click();
});
// Global buttons
@ -131,4 +130,17 @@ Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => {
}
});
});
Cypress.Commands.add('openListSummary', (row) => {
cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(row).click();
});
Cypress.Commands.add('openRightMenu', (element) => {
if (element) cy.waitForElement(element);
cy.get('#actions-append').click();
});
Cypress.Commands.add('validateContent', (selector, expectedValue) => {
cy.get(selector).should('have.text', expectedValue);
});
// registerCommands();

View File

@ -1,75 +1,134 @@
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import { createWrapper, axios } from 'app/test/vitest/helper';
import VnLog from 'src/components/common/VnLog.vue';
describe('VnLog', () => {
let vm;
beforeAll(() => {
vm = createWrapper(VnLog, {
global: {
stubs: ['FetchData', 'VnPaginate'],
mocks: {
fetch: vi.fn(),
const fakeLogTreeData = [
{
id: 2,
originFk: 1,
userFk: 18,
action: 'update',
changedModel: 'ClaimObservation',
oldInstance: {},
newInstance: {
claimFk: 1,
text: 'Waiting for customer',
},
creationDate: '2023-09-18T12:25:34.000Z',
changedModelId: '1',
changedModelValue: null,
description: null,
user: {
id: 18,
name: 'salesPerson',
nickname: 'salesPersonNick',
image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd',
worker: {
id: 18,
userFk: 18,
},
},
},
{
id: 1,
originFk: 1,
userFk: 18,
action: 'update',
changedModel: 'Claim',
oldInstance: {
hasToPickUp: false,
},
newInstance: {
hasToPickUp: true,
},
creationDate: '2023-09-18T12:25:34.000Z',
changedModelId: '1',
changedModelValue: null,
description: null,
user: {
id: 18,
name: 'salesPerson',
nickname: 'salesPersonNick',
image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd',
worker: {
id: 18,
userFk: 18,
},
},
},
];
const mockValidations = {
Claim: {
locale: {
name: 'reclamación',
},
},
ClaimObservation: {
locale: {
name: 'observación',
},
},
ClaimDms: {
locale: {
name: 'documento',
},
},
ClaimBeginning: {
locale: {
name: 'comienzo',
},
},
};
beforeAll(async () => {
axios.get.mockImplementation(() => {
return { data: fakeLogTreeData };
});
vm = createWrapper(VnLog, {
global: {
stubs: [],
mocks: {},
},
propsData: {
model: "Claim",
model: 'Claim',
},
}).vm;
vm.validations = mockValidations;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('formatValue()', () => {
it('should return Yes if has a true boolean', async () => {
const result = vm.formatValue(true);
expect(result).toEqual('Yes');
});
it('should return No if has a true boolean', async () => {
const result = vm.formatValue(false);
expect(result).toEqual('No');
});
it('should return Nothing if has no params', async () => {
const result = vm.formatValue();
expect(result).toEqual('Nothing');
});
it('should return a string from a string value', async () => {
const result = vm.formatValue('Something');
expect(result).toEqual(`"Something"`);
});
it('should call to format a date', async () => {
vi.mock('src/filters', () => ({
toDate: ()=>{
return "Date formatted"
},
}));
const result = vm.formatValue('01-01-1970');
expect(result).toEqual("Date formatted");
});
it('should correctly set logTree', async () => {
vm.logTree = vm.getLogs(fakeLogTreeData);
expect(vm.logTree[0].originFk).toEqual(1);
expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson');
});
describe('actionColor()', () => {
it('should return positive if insert', async () => {
const result = vm.actionColor('insert');
it('should correctly set the selectedFilters when filtering', () => {
vm.searchInput = '1';
vm.userSelect = '21';
vm.checkboxOptions.insert.selected = true;
vm.checkboxOptions.update.selected = true;
expect(result).toEqual('positive');
});
it('should return positive if update', async () => {
const result = vm.actionColor('update');
vm.selectFilter('search');
vm.selectFilter('userSelect');
expect(result).toEqual('positive');
});
it('should return negative if delete', async () => {
const result = vm.actionColor('delete');
expect(vm.selectedFilters.changedModelId).toEqual('1');
expect(vm.selectedFilters.userFk).toEqual('21');
expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] });
});
expect(result).toEqual('negative');
});
it('should correctly set the date from', () => {
vm.dateFrom = '18-09-2023';
vm.selectFilter('date', 'from');
expect(vm.selectedFilters.creationDate.between).toEqual([
new Date('2023-09-18T00:00:00.000Z'),
new Date('2023-09-18T21:59:59.999Z'),
]);
});
});

View File

@ -13,7 +13,6 @@ installQuasarPlugin({
Dialog,
},
});
const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false });
jorgep marked this conversation as resolved Outdated
Outdated
Review

No crec que en test de front el front dega fer peticions al back.
Deuries poder tirar els test de front sense tindre arrancat res

No crec que en test de front el front dega fer peticions al back. Deuries poder tirar els test de front sense tindre arrancat res
const mockPush = vi.fn();
@ -35,8 +34,10 @@ vi.mock('vue-router', () => ({
}),
}));
vi.mock('axios');
vi.spyOn(useValidator, 'useValidator').mockImplementation(() => {
return { validate: vi.fn(), fetch: vi.fn() };
return { validate: vi.fn() };
});
class FormDataMock {