Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8277-createEntryControl
gitea/salix-front/pipeline/pr-dev This commit is unstable Details

This commit is contained in:
Jorge Penadés 2025-04-16 17:54:01 +02:00
commit b5b69055bd
20 changed files with 639 additions and 148 deletions

View File

@ -44,6 +44,7 @@ export default defineConfig({
supportFile: 'test/cypress/support/index.js',
videosFolder: 'test/cypress/videos',
downloadsFolder: 'test/cypress/downloads',
tmpUploadFolder: 'test/cypress/storage/tmp/dms',
video: false,
specPattern: 'test/cypress/integration/**/*.spec.js',
experimentalRunAllSpecs: true,

View File

@ -4,6 +4,7 @@ import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
@ -12,6 +13,7 @@ import FormModelPopup from 'components/FormModelPopup.vue';
const route = useRoute();
const { t } = useI18n();
const { notify } = useNotify();
const emit = defineEmits(['onDataSaved']);
const $props = defineProps({
@ -86,11 +88,16 @@ function getUrl() {
}
async function save() {
const body = mapperDms(dms.value);
const response = await axios.post(getUrl(), body[0], body[1]);
emit('onDataSaved', body[1].params, response);
delete dms.value.files;
return response;
try {
const body = mapperDms(dms.value);
const response = await axios.post(getUrl(), body[0], body[1]);
emit('onDataSaved', body[1].params, response);
notify(t('globals.dataSaved'), 'positive');
delete dms.value.files;
return response;
} catch (e) {
throw e;
}
}
function defaultData() {
@ -211,7 +218,7 @@ function addDefaultData(data) {
}
</style>
<i18n>
en:
en:
contentTypesInfo: Allowed file types {allowedContentTypes}
EntryDmsDescription: Reference {reference}
WorkersDescription: Working of employee id {reference}

View File

@ -13,10 +13,12 @@ import VnDms from 'src/components/common/VnDms.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import { useSession } from 'src/composables/useSession';
import useNotify from 'src/composables/useNotify.js';
const route = useRoute();
const quasar = useQuasar();
const { t } = useI18n();
const { notify } = useNotify();
const rows = ref([]);
const dmsRef = ref();
const formDialog = ref({});
@ -88,7 +90,6 @@ const dmsFilter = {
],
},
},
where: { [$props.filter]: route.params.id },
};
const columns = computed(() => [
@ -258,9 +259,16 @@ function deleteDms(dmsFk) {
},
})
.onOk(async () => {
await axios.post(`${$props.deleteModel ?? $props.model}/${dmsFk}/removeFile`);
const index = rows.value.findIndex((row) => row.id == dmsFk);
rows.value.splice(index, 1);
try {
await axios.post(
`${$props.deleteModel ?? $props.model}/${dmsFk}/removeFile`,
);
const index = rows.value.findIndex((row) => row.id == dmsFk);
rows.value.splice(index, 1);
notify(t('globals.dataDeleted'), 'positive');
} catch (e) {
throw e;
}
});
}
@ -298,7 +306,9 @@ defineExpose({
:data-key="$props.model"
:url="$props.model"
:user-filter="dmsFilter"
search-url="dmsFilter"
:order="['dmsFk DESC']"
:filter="{ where: { [$props.filter]: route.params.id } }"
auto-load
@on-fetch="setData"
>

View File

@ -1,6 +1,6 @@
<script setup>
import { onMounted, watch, computed, ref, useAttrs } from 'vue';
import { date } from 'quasar';
import { nextTick, watch, computed, ref, useAttrs } from 'vue';
import { date, getCssVar } from 'quasar';
import VnDate from './VnDate.vue';
import { useRequired } from 'src/composables/useRequired';
@ -20,61 +20,18 @@ const $props = defineProps({
});
const vnInputDateRef = ref(null);
const errColor = getCssVar('negative');
const textColor = ref('');
const dateFormat = 'DD/MM/YYYY';
const isPopupOpen = ref();
const hover = ref();
const mask = ref();
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
const formattedDate = computed({
get() {
if (!model.value) return model.value;
return date.formatDate(new Date(model.value), dateFormat);
},
set(value) {
if (value == model.value) return;
let newDate;
if (value) {
// parse input
if (value.includes('/') && value.length >= 10) {
if (value.at(2) == '/') value = value.split('/').reverse().join('/');
value = date.formatDate(
new Date(value).toISOString(),
'YYYY-MM-DDTHH:mm:ss.SSSZ',
);
}
const [year, month, day] = value.split('-').map((e) => parseInt(e));
newDate = new Date(year, month - 1, day);
if (model.value) {
const orgDate =
model.value instanceof Date ? model.value : new Date(model.value);
newDate.setHours(
orgDate.getHours(),
orgDate.getMinutes(),
orgDate.getSeconds(),
orgDate.getMilliseconds(),
);
}
}
if (!isNaN(newDate)) model.value = newDate.toISOString();
},
});
const popupDate = computed(() =>
model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value,
);
onMounted(() => {
// fix quasar bug
mask.value = '##/##/####';
});
watch(
() => model.value,
(val) => (formattedDate.value = val),
{ immediate: true },
);
const styleAttrs = computed(() => {
return $props.isOutlined
@ -86,28 +43,139 @@ const styleAttrs = computed(() => {
: {};
});
const inputValue = ref('');
const validateAndCleanInput = (value) => {
inputValue.value = value.replace(/[^0-9./-]/g, '');
};
const manageDate = (date) => {
formattedDate.value = date;
inputValue.value = date.split('/').reverse().join('/');
isPopupOpen.value = false;
};
watch(
() => model.value,
(nVal) => {
if (nVal) inputValue.value = date.formatDate(new Date(model.value), dateFormat);
else inputValue.value = '';
},
{ immediate: true },
);
const formatDate = () => {
let value = inputValue.value;
if (!value || value === model.value) {
textColor.value = '';
return;
}
const regex =
/^([0]?[1-9]|[12][0-9]|3[01])([./-])([0]?[1-9]|1[0-2])([./-](\d{1,4}))?$/;
if (!regex.test(value)){
textColor.value = errColor;
return;
}
value = value.replace(/[.-]/g, '/');
const parts = value.split('/');
if (parts.length < 2) {
textColor.value = errColor;
return;
}
let [day, month, year] = parts;
if (day.length === 1) day = '0' + day;
if (month.length === 1) month = '0' + month;
const currentYear = Date.vnNew().getFullYear();
if (!year) year = currentYear;
const millennium = currentYear.toString().slice(0, 1);
switch (year.length) {
case 1:
year = `${millennium}00${year}`;
break;
case 2:
year = `${millennium}0${year}`;
break;
case 3:
year = `${millennium}${year}`;
break;
case 4:
break;
}
let isoCandidate = `${year}/${month}/${day}`;
isoCandidate = date.formatDate(
new Date(isoCandidate).toISOString(),
'YYYY-MM-DDTHH:mm:ss.SSSZ',
);
const [isoYear, isoMonth, isoDay] = isoCandidate.split('-').map((e) => parseInt(e));
const parsedDate = new Date(isoYear, isoMonth - 1, isoDay);
const isValidDate =
parsedDate instanceof Date &&
!isNaN(parsedDate) &&
parsedDate.getFullYear() === parseInt(year) &&
parsedDate.getMonth() === parseInt(month) - 1 &&
parsedDate.getDate() === parseInt(day);
if (!isValidDate) {
textColor.value = errColor;
return;
}
if (model.value) {
const original =
model.value instanceof Date ? model.value : new Date(model.value);
parsedDate.setHours(
original.getHours(),
original.getMinutes(),
original.getSeconds(),
original.getMilliseconds(),
);
}
model.value = parsedDate.toISOString();
textColor.value = '';
};
const handleEnter = (event) => {
formatDate();
nextTick(() => {
const newEvent = new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
bubbles: true,
cancelable: true,
});
vnInputDateRef.value?.$el?.dispatchEvent(newEvent);
});
};
</script>
<template>
<div @mouseover="hover = true" @mouseleave="hover = false">
{{ console.log($q) }}
<QInput
ref="vnInputDateRef"
v-model="formattedDate"
v-model="inputValue"
class="vn-input-date"
:mask="mask"
placeholder="dd/mm/aaaa"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: isRequired }"
:rules="mixinRules"
:clearable="false"
:input-style="{color: textColor}"
@click="isPopupOpen = !isPopupOpen"
@keydown="isPopupOpen = false"
@blur="formatDate"
@keydown.enter.prevent="handleEnter"
hide-bottom-space
:data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'"
@update:model-value="validateAndCleanInput"
>
<template #append>
<QIcon
@ -116,11 +184,12 @@ const manageDate = (date) => {
v-if="
($attrs.clearable == undefined || $attrs.clearable) &&
hover &&
model &&
inputValue &&
!$attrs.disable
"
@click="
vnInputDateRef.focus();
inputValue = null;
model = null;
isPopupOpen = false;
"

View File

@ -5,52 +5,71 @@ import VnInputDate from 'components/common/VnInputDate.vue';
let vm;
let wrapper;
function generateWrapper(date, outlined, required) {
function generateWrapper(outlined = false, required = false) {
wrapper = createWrapper(VnInputDate, {
props: {
modelValue: date,
modelValue: '2000-12-31T23:00:00.000Z',
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
},
attrs: {
isOutlined: outlined,
required: required
required: required,
},
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
};
}
describe('VnInputDate', () => {
describe('formattedDate', () => {
it('formats a valid date correctly', async () => {
generateWrapper('2023-12-25', false, false);
describe('formattedDate', () => {
it('validateAndCleanInput should remove non-numeric characters', async () => {
generateWrapper();
vm.validateAndCleanInput('10a/1s2/2dd0a23');
await vm.$nextTick();
expect(vm.formattedDate).toBe('25/12/2023');
expect(vm.inputValue).toBe('10/12/2023');
});
it('updates the model value when a new date is set', async () => {
const input = wrapper.find('input');
await input.setValue('31/12/2023');
expect(wrapper.emitted()['update:modelValue']).toBeTruthy();
expect(wrapper.emitted()['update:modelValue'][0][0]).toBe('2023-12-31T00:00:00.000Z');
it('manageDate should reverse the date', async () => {
generateWrapper();
vm.manageDate('10/12/2023');
await vm.$nextTick();
expect(vm.inputValue).toBe('2023/12/10');
});
it('should not update the model value when an invalid date is set', async () => {
it('formatDate should format the date correctly when a valid date is entered with full year', async () => {
const input = wrapper.find('input');
await input.setValue('invalid-date');
expect(wrapper.emitted()['update:modelValue'][0][0]).toBe('2023-12-31T00:00:00.000Z');
});
await input.setValue('25.12/2002');
await vm.$nextTick();
await vm.formatDate();
expect(vm.model).toBe('2002-12-24T23:00:00.000Z');
});
it('should format the date correctly when a valid date is entered with short year', async () => {
const input = wrapper.find('input');
await input.setValue('31.12-23');
await vm.$nextTick();
await vm.formatDate();
expect(vm.model).toBe('2023-12-30T23:00:00.000Z');
});
it('should format the date correctly when a valid date is entered without year', async () => {
const input = wrapper.find('input');
await input.setValue('12.03');
await vm.$nextTick();
await vm.formatDate();
expect(vm.model).toBe('2001-03-11T23:00:00.000Z');
});
});
describe('styleAttrs', () => {
it('should return empty styleAttrs when isOutlined is false', async () => {
generateWrapper('2023-12-25', false, false);
generateWrapper();
await vm.$nextTick();
expect(vm.styleAttrs).toEqual({});
expect(vm.styleAttrs).toEqual({});
});
it('should set styleAttrs when isOutlined is true', async () => {
generateWrapper('2023-12-25', true, false);
it('should set styleAttrs when isOutlined is true', async () => {
generateWrapper(true, false);
await vm.$nextTick();
expect(vm.styleAttrs.outlined).toBe(true);
});
@ -58,15 +77,15 @@ describe('VnInputDate', () => {
describe('required', () => {
it('should not applies required class when isRequired is false', async () => {
generateWrapper('2023-12-25', false, false);
generateWrapper();
await vm.$nextTick();
expect(wrapper.find('.vn-input-date').classes()).not.toContain('required');
});
it('should applies required class when isRequired is true', async () => {
generateWrapper('2023-12-25', false, true);
generateWrapper(false, true);
await vm.$nextTick();
expect(wrapper.find('.vn-input-date').classes()).toContain('required');
});
});
});
});

View File

@ -162,6 +162,9 @@ globals:
department: Department
noData: No data available
vehicle: Vehicle
selectDocumentId: Select document id
document: Document
import: Import from existing
pageTitles:
logIn: Login
addressEdit: Update address

View File

@ -166,6 +166,9 @@ globals:
noData: Datos no disponibles
department: Departamento
vehicle: Vehículo
selectDocumentId: Seleccione el id de gestión documental
document: Documento
import: Importar desde existente
pageTitles:
logIn: Inicio de sesión
addressEdit: Modificar consignatario

View File

@ -164,6 +164,7 @@ onMounted(async () => {
unelevated
filled
dense
data-cy="formSubmitBtn"
/>
<QBtn
v-else
@ -174,6 +175,7 @@ onMounted(async () => {
filled
dense
@click="getStatus = 'stopping'"
data-cy="formStopBtn"
/>
</QForm>
</template>

View File

@ -0,0 +1,65 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnSelect from 'src/components/common/VnSelect.vue';
import FormModelPopup from 'components/FormModelPopup.vue';
import FetchData from 'components/FetchData.vue';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const { notify } = useNotify();
const route = useRoute();
const dmsOptions = ref([]);
const dmsId = ref(null);
const importDms = async () => {
try {
if (!dmsId.value) throw new Error(t(`vehicle.errors.documentIdEmpty`));
const data = {
vehicleFk: route.params.id,
dmsFk: dmsId.value,
};
await axios.post('vehicleDms', data);
notify(t('globals.dataSaved'), 'positive');
dmsId.value = null;
emit('onDataSaved');
} catch (e) {
throw e;
}
};
</script>
<template>
<FetchData
url="Dms"
:filter="{ fields: ['id'], order: 'id ASC' }"
auto-load
@on-fetch="(data) => (dmsOptions = data)"
/>
<FormModelPopup
model="DmsImport"
:title="t('globals.selectDocumentId')"
:form-initial-data="{}"
:save-fn="importDms"
>
<template #form-inputs>
<VnSelect
:label="t('globals.document')"
:options="dmsOptions"
hide-selected
option-label="id"
option-value="id"
v-model="dmsId"
/>
</template>
</FormModelPopup>
</template>

View File

@ -0,0 +1,42 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnDmsList from 'src/components/common/VnDmsList.vue';
import VehicleDmsImportForm from 'src/pages/Route/Vehicle/Card/VehicleDmsImportForm.vue';
const { t } = useI18n();
const dmsListRef = ref(null);
const showImportDialog = ref(false);
const onDataSaved = () => dmsListRef.value.dmsRef.fetch();
</script>
<template>
<VnDmsList
ref="dmsListRef"
model="VehicleDms"
update-model="vehicles"
delete-model="VehicleDms"
download-model="dms"
default-dms-code="vehicles"
filter="vehicleFk"
/>
<QDialog v-model="showImportDialog">
<VehicleDmsImportForm @on-data-saved="onDataSaved()" />
</QDialog>
<QPageSticky position="bottom-right" :offset="[25, 90]">
<QBtn
fab
color="primary"
icon="file_copy"
@click="showImportDialog = true"
class="fill-icon"
data-cy="importBtn"
>
<QTooltip>
{{ t('globals.import') }}
</QTooltip>
</QBtn>
</QPageSticky>
</template>

View File

@ -18,3 +18,5 @@ vehicle:
params:
vehicleTypeFk: Type
vehicleStateFk: State
errors:
documentIdEmpty: The document identifier can't be empty

View File

@ -18,3 +18,5 @@ vehicle:
params:
vehicleTypeFk: Tipo
vehicleStateFk: Estado
errors:
documentIdEmpty: El número de documento no puede estar vacío

View File

@ -34,7 +34,7 @@ const importDms = async () => {
dmsId.value = null;
emit('onDataSaved');
} catch (e) {
throw new Error(e.message);
throw e;
}
};
</script>
@ -49,7 +49,7 @@ const importDms = async () => {
<FormModelPopup
url-create="genera"
model="DmsImport"
:title="t('Select document id')"
:title="t('globals.selectDocumentId')"
:form-initial-data="{}"
:save-fn="importDms"
>
@ -70,7 +70,6 @@ const importDms = async () => {
<i18n>
es:
Select document id: Introduzca id de gestion documental
Document: Documento
The document indentifier can't be empty: El número de documento no puede estar vacío
</i18n>

View File

@ -166,7 +166,11 @@ const vehicleCard = {
component: () => import('src/pages/Route/Vehicle/Card/VehicleCard.vue'),
redirect: { name: 'VehicleSummary' },
meta: {
menu: ['VehicleBasicData', 'VehicleNotes'],
menu: [
'VehicleBasicData',
'VehicleNotes',
'VehicleDms',
],
},
children: [
{
@ -195,7 +199,16 @@ const vehicleCard = {
icon: 'vn:notes',
},
component: () => import('src/pages/Route/Vehicle/Card/VehicleNotes.vue'),
}
},
{
name: 'VehicleDms',
path: 'dms',
meta: {
title: 'dms',
icon: 'cloud_upload',
},
component: () => import('src/pages/Route/Vehicle/VehicleDms.vue'),
},
],
};

View File

@ -10,7 +10,7 @@ describe('EntryDms', () => {
cy.dataCy('recalc').should('be.disabled');
cy.dataCy('dateFrom').should('be.visible').click().type('01-01-2001');
cy.dataCy('dateTo').should('be.visible').click().type('01-01-2001');
cy.dataCy('dateTo').should('be.visible').click().type('01-01-2001{enter}');
cy.dataCy('recalc').should('be.enabled').click();
cy.get('.q-notification__message').should(

View File

@ -22,6 +22,7 @@ describe('InvoiceOut global invoicing', () => {
cy.get('.q-date__years-content > :nth-child(2) > .q-btn').click();
cy.get('.q-date__calendar-days > :nth-child(6) > .q-btn').click();
cy.get('[label="Max date ticket"]').type('01-01-2001{enter}');
cy.dataCy('formSubmitBtn').click();
cy.get('.q-card').should('be.visible');
});
});

View File

@ -1,11 +1,12 @@
describe('RouteAutonomous', () => {
const getLinkSelector = (colField) =>
`tr:first-child > [data-col-field="${colField}"] > .no-padding > .link`;
const getLinkSelector = (colField, link = true) =>
`tr:first-child > [data-col-field="${colField}"] > .no-padding${link ? ' > .link' : ''}`;
const selectors = {
reference: 'Reference_input',
date: 'tr:first-child > [data-col-field="dated"]',
total: '.value > .text-h6',
routeId: getLinkSelector('routeFk', false),
agencyRoute: getLinkSelector('agencyModeName'),
agencyAgreement: getLinkSelector('agencyAgreement'),
received: getLinkSelector('invoiceInFk'),
autonomous: getLinkSelector('supplierName'),
firstRowCheckbox: '.q-virtual-scroll__content tr:first-child .q-checkbox__bg',
@ -13,22 +14,30 @@ describe('RouteAutonomous', () => {
createInvoiceBtn: '.q-card > .q-btn',
saveFormBtn: 'FormModelPopup_save',
summaryIcon: 'tableAction-0',
summaryPopupBtn: '.header > :nth-child(2) > .q-btn__content > .q-icon',
summaryHeader: '.summaryHeader > :nth-child(2)',
descriptorHeader: '.summaryHeader > div',
descriptorTitle: '.q-item__label--header > .title > span',
summaryGoToSummaryBtn: '.header > .q-icon',
descriptorGoToSummaryBtn: '.descriptor > .header > a[href] > .q-btn',
descriptorRouteSubtitle: '[data-cy="vnDescriptor_subtitle"]',
descriptorAgencyAndSupplierTitle: '[data-cy="vnDescriptor_description"]',
descriptorInvoiceInTitle: '[data-cy="vnDescriptor_title"]',
descriptorOpenSummaryBtn: '.q-menu > .descriptor [data-cy="openSummaryBtn"]',
descriptorGoToSummaryBtn: '.q-menu > .descriptor [data-cy="goToSummaryBtn"]',
summaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]',
};
const data = {
reference: 'Test invoice',
total: '€206.40',
supplier: 'PLANTS SL',
route: 'first route',
const newInvoice = {
Reference: { val: 'Test invoice' },
Company: { val: 'VNL', type: 'select' },
Warehouse: { val: 'Warehouse One', type: 'select' },
Type: { val: 'Vehiculos', type: 'select' },
Description: { val: 'Test description' },
};
const summaryUrl = '/summary';
const total = '€206.40';
const urls = {
summaryAgencyUrlRegex: /agency\/\d+\/summary/,
summaryInvoiceInUrlRegex: /invoice-in\/\d+\/summary/,
summarySupplierUrlRegex: /supplier\/\d+\/summary/,
summaryRouteUrlRegex: /route\/\d+\/summary/,
};
const dataSaved = 'Data saved';
beforeEach(() => {
@ -47,7 +56,7 @@ describe('RouteAutonomous', () => {
it.skip('Should create invoice in to selected route', () => {
cy.get(selectors.firstRowCheckbox).click();
cy.get(selectors.createInvoiceBtn).click();
cy.dataCy(selectors.reference).type(data.reference);
cy.fillInForm(newInvoice);
cy.dataCy('attachFile').click();
cy.get('.q-file').selectFile('test/cypress/fixtures/image.jpg', {
force: true,
@ -59,62 +68,120 @@ describe('RouteAutonomous', () => {
it('Should display the total price of the selected rows', () => {
cy.get(selectors.firstRowCheckbox).click();
cy.get(selectors.secondRowCheckbox).click();
cy.validateContent(selectors.total, data.total);
cy.validateContent(selectors.total, total);
});
it('Should redirect to the summary when clicking a route', () => {
cy.get(selectors.date).click();
cy.get(selectors.summaryHeader).should('contain', data.route);
cy.url().should('include', summaryUrl);
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.routeId,
expectedUrlRegex: urls.summaryRouteUrlRegex,
expectedTextSelector: selectors.descriptorRouteSubtitle,
});
});
describe.skip('Received pop-ups', () => {
it('Should redirect to invoice in summary from the received descriptor pop-up', () => {
cy.get(selectors.received).click();
cy.validateContent(selectors.descriptorTitle, data.reference);
cy.get(selectors.descriptorGoToSummaryBtn).click();
cy.get(selectors.descriptorHeader).should('contain', data.supplier);
cy.url().should('include', summaryUrl);
describe('Agency route pop-ups', () => {
it('Should redirect to the agency route summary from the agency route descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.agencyRoute,
steps: [selectors.descriptorGoToSummaryBtn],
expectedUrlRegex: urls.summaryAgencyUrlRegex,
expectedTextSelector: selectors.descriptorAgencyAndSupplierTitle,
});
});
it('Should redirect to the agency route summary from summary pop-up from the agency route descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.agencyRoute,
steps: [
selectors.descriptorOpenSummaryBtn,
selectors.summaryGoToSummaryBtn,
],
expectedUrlRegex: urls.summaryAgencyUrlRegex,
expectedTextSelector: selectors.descriptorAgencyAndSupplierTitle,
});
});
});
describe('Agency route pop-ups', () => {
it('Should redirect to the agency agreement summary from the agency agreement descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.agencyAgreement,
steps: [selectors.descriptorGoToSummaryBtn],
expectedUrlRegex: urls.summaryAgencyUrlRegex,
expectedTextSelector: selectors.descriptorAgencyAndSupplierTitle,
});
});
it('Should redirect to the agency agreement summary from summary pop-up from the agency agreement descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.agencyAgreement,
steps: [
selectors.descriptorOpenSummaryBtn,
selectors.summaryGoToSummaryBtn,
],
expectedUrlRegex: urls.summaryAgencyUrlRegex,
expectedTextSelector: selectors.descriptorAgencyAndSupplierTitle,
});
});
});
describe('Received pop-ups', () => {
it('Should redirect to the invoice in summary from the received descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.received,
steps: [selectors.descriptorGoToSummaryBtn],
expectedUrlRegex: urls.summaryInvoiceInUrlRegex,
expectedTextSelector: selectors.descriptorInvoiceInTitle,
});
});
it('Should redirect to the invoiceIn summary from summary pop-up from the received descriptor pop-up', () => {
cy.get(selectors.received).click();
cy.validateContent(selectors.descriptorTitle, data.reference);
cy.get(selectors.summaryPopupBtn).click();
cy.get(selectors.descriptorHeader).should('contain', data.supplier);
cy.get(selectors.summaryGoToSummaryBtn).click();
cy.get(selectors.descriptorHeader).should('contain', data.supplier);
cy.url().should('include', summaryUrl);
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.received,
steps: [
selectors.descriptorOpenSummaryBtn,
selectors.summaryGoToSummaryBtn,
],
expectedUrlRegex: urls.summaryInvoiceInUrlRegex,
expectedTextSelector: selectors.descriptorInvoiceInTitle,
});
});
});
describe('Autonomous pop-ups', () => {
it('Should redirect to the supplier summary from the received descriptor pop-up', () => {
cy.get(selectors.autonomous).click();
cy.validateContent(selectors.descriptorTitle, data.supplier);
cy.get(selectors.descriptorGoToSummaryBtn).click();
cy.get(selectors.summaryHeader).should('contain', data.supplier);
cy.url().should('include', summaryUrl);
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.autonomous,
steps: [selectors.descriptorGoToSummaryBtn],
expectedUrlRegex: urls.summarySupplierUrlRegex,
expectedTextSelector: selectors.descriptorAgencyAndSupplierTitle,
});
});
it('Should redirect to the supplier summary from summary pop-up from the autonomous descriptor pop-up', () => {
cy.get(selectors.autonomous).click();
cy.get(selectors.descriptorTitle).should('contain', data.supplier);
cy.get(selectors.summaryPopupBtn).click();
cy.get(selectors.summaryHeader).should('contain', data.supplier);
cy.get(selectors.summaryGoToSummaryBtn).click();
cy.get(selectors.summaryHeader).should('contain', data.supplier);
cy.url().should('include', summaryUrl);
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.autonomous,
steps: [
selectors.descriptorOpenSummaryBtn,
selectors.summaryGoToSummaryBtn,
],
expectedUrlRegex: urls.summarySupplierUrlRegex,
expectedTextSelector: selectors.descriptorAgencyAndSupplierTitle,
});
});
});
describe('Route pop-ups', () => {
it('Should redirect to the summary from the route summary pop-up', () => {
cy.dataCy(selectors.summaryIcon).first().click();
cy.get(selectors.summaryHeader).should('contain', data.route);
cy.get(selectors.summaryGoToSummaryBtn).click();
cy.get(selectors.summaryHeader).should('contain', data.route);
cy.url().should('include', summaryUrl);
cy.get(selectors.routeId)
.invoke('text')
.then((routeId) => {
routeId = routeId.trim();
cy.dataCy(selectors.summaryIcon).first().click();
cy.get(selectors.summaryGoToSummaryBtn).click();
cy.url().should('match', urls.summaryRouteUrlRegex);
cy.containContent(selectors.descriptorRouteSubtitle, routeId);
});
});
});
});

View File

@ -69,7 +69,8 @@ describe.skip('Route extended list', () => {
.type(`{selectall}{backspace}${value}`);
break;
case 'checkbox':
cy.get(selector).should('be.visible').click().click();
cy.get(selector).should('be.visible').click()
cy.get(selector).click();
break;
}
}

View File

@ -0,0 +1,147 @@
describe('Vehicle DMS', () => {
const getSelector = (btnPosition) =>
`tr:last-child > .text-right > .no-wrap > :nth-child(${btnPosition}) > .q-btn > .q-btn__content > .q-icon`;
const selectors = {
lastRowDownloadBtn: getSelector(1),
lastRowEditBtn: getSelector(2),
lastRowDeleteBtn: getSelector(3),
lastRowReference: 'tr:last-child > :nth-child(5) > .q-tr > :nth-child(1) > span',
firstRowReference:
'tr:first-child > :nth-child(5) > .q-tr > :nth-child(1) > span',
firstRowId: 'tr:first-child > :nth-child(2) > .q-tr > :nth-child(1) > span',
lastRowWorkerLink: 'tr:last-child > :nth-child(8) > .q-tr > .link',
descriptorTitle: '.descriptor .title',
descriptorOpenSummaryBtn: '.q-menu .descriptor [data-cy="openSummaryBtn"]',
descriptorGoToSummaryBtn: '.q-menu .descriptor [data-cy="goToSummaryBtn"]',
summaryGoToSummaryBtn: '.summaryHeader [data-cy="goToSummaryBtn"]',
summaryTitle: '.summaryHeader',
referenceInput: 'Reference_input',
companySelect: 'Company_select',
warehouseSelect: 'Warehouse_select',
typeSelect: 'Type_select',
fileInput: 'VnDms_inputFile',
importBtn: '[data-cy="importBtn"]',
addBtn: '[data-cy="addButton"]',
saveFormBtn: 'FormModelPopup_save',
};
const data = {
Reference: { val: 'Vehicle:1234-ABC' },
Company: { val: 'VNL', type: 'select' },
Warehouse: { val: 'Warehouse One', type: 'select' },
Type: { val: 'Vehiculos', type: 'select' },
};
const updateData = {
Reference: { val: 'Vehicle:4598-FGH' },
Company: { val: 'CCs', type: 'select' },
Warehouse: { val: 'Warehouse Two', type: 'select' },
Type: { val: 'Facturas Recibidas', type: 'select' },
};
const workerSummaryUrlRegex = /worker\/\d+\/summary/;
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/route/vehicle/1/dms`);
});
it('should display vehicle DMS', () => {
cy.get('.q-table')
.children()
.should('be.visible')
.should('have.length.greaterThan', 0);
});
it.skip('Should download DMS', () => {
const fileName = '11.jpg';
cy.intercept('GET', /\/api\/dms\/11\/downloadFile/).as('download');
cy.get(selectors.lastRowDownloadBtn).click();
cy.wait('@download').then((interception) => {
expect(interception.response.statusCode).to.equal(200);
expect(interception.response.headers['content-disposition']).to.contain(
fileName,
);
});
});
it('Should create new DMS', () => {
const formSelectors = {
actionBtn: selectors.addBtn,
fileInput: selectors.fileInput,
saveFormBtn: selectors.saveFormBtn,
};
cy.testDmsAction('create', formSelectors, data, 'Data saved');
});
it('Should import DMS', () => {
const data = {
Document: { val: '10', type: 'select' },
};
const formSelectors = {
actionBtn: selectors.importBtn,
selectorContentToCheck: selectors.lastRowReference,
saveFormBtn: selectors.saveFormBtn,
};
cy.testDmsAction('import', formSelectors, data, 'Data saved', '1');
});
it('Should edit DMS', () => {
const formSelectors = {
actionBtn: selectors.lastRowEditBtn,
selectorContentToCheck: selectors.lastRowReference,
saveFormBtn: selectors.saveFormBtn,
};
cy.testDmsAction(
'edit',
formSelectors,
updateData,
'Data saved',
updateData.Reference.val,
);
});
it('Should delete DMS', () => {
const formSelectors = {
actionBtn: selectors.lastRowDeleteBtn,
selectorContentToCheck: selectors.lastRowReference,
};
cy.testDmsAction(
'delete',
formSelectors,
null,
'Data deleted',
'Vehicle:3333-BAT',
);
});
describe('Worker pop-ups', () => {
it('Should redirect to worker summary from worker descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.lastRowWorkerLink,
steps: [selectors.descriptorGoToSummaryBtn],
expectedUrlRegex: workerSummaryUrlRegex,
expectedTextSelector: selectors.descriptorTitle,
});
});
it('Should redirect to worker summary from summary pop-up from worker descriptor pop-up', () => {
cy.checkRedirectionFromPopUp({
selectorToClick: selectors.lastRowWorkerLink,
steps: [
selectors.descriptorOpenSummaryBtn,
selectors.summaryGoToSummaryBtn,
],
expectedUrlRegex: workerSummaryUrlRegex,
expectedTextSelector: selectors.descriptorTitle,
});
});
});
});

View File

@ -195,8 +195,8 @@ Cypress.Commands.add('fillInForm', (obj, opts = {}) => {
break;
case 'date':
cy.get(el).type(
`{selectall}{backspace}${val.split('-').join('')}`,
);
`{selectall}{backspace}${val}`,
).blur();
break;
case 'time':
cy.get(el).click();
@ -635,3 +635,41 @@ Cypress.Commands.add('validateScrollContent', (validations) => {
);
});
});
Cypress.Commands.add(
'checkRedirectionFromPopUp',
({ selectorToClick, steps = [], expectedUrlRegex, expectedTextSelector }) => {
cy.get(selectorToClick)
.click()
.invoke('text')
.then((label) => {
label = label.trim();
steps.forEach((stepSelector) => {
cy.get(stepSelector).should('be.visible').click();
});
cy.location().should('match', expectedUrlRegex);
cy.containContent(expectedTextSelector, label);
});
},
);
Cypress.Commands.add('testDmsAction', (action, selectors, data, message, content) => {
cy.get(selectors.actionBtn).click();
if (action === 'create') {
cy.dataCy(selectors.fileInput).selectFile('test/cypress/fixtures/image.jpg', {
force: true,
});
}
if (action !== 'delete') {
cy.fillInForm(data);
cy.dataCy(selectors.saveFormBtn).click();
} else cy.clickConfirm();
cy.checkNotification(message);
if (action !== 'create') cy.containContent(selectors.selectorContentToCheck, content);
});