refactor: refs #6897 clean up alignment and improve data attributes for better testing

This commit is contained in:
Pablo Natek 2025-02-10 11:41:41 +01:00
parent af2cbda077
commit 5f624bbf7f
9 changed files with 99 additions and 73 deletions

View File

@ -46,7 +46,6 @@ const enterEvent = {
const defaultAttrs = { const defaultAttrs = {
filled: !$props.showTitle, filled: !$props.showTitle,
// class: 'q-px-xs q-pb-xs q-pt-none fit',
dense: true, dense: true,
}; };

View File

@ -137,6 +137,7 @@ const $props = defineProps({
type: Object, type: Object,
}, },
}); });
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
@ -165,6 +166,7 @@ const editingField = ref(null);
const isTableMode = computed(() => mode.value == TABLE_MODE); const isTableMode = computed(() => mode.value == TABLE_MODE);
const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon); const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon);
const selectRegex = /select/; const selectRegex = /select/;
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
const tableModes = [ const tableModes = [
{ {
icon: 'view_column', icon: 'view_column',
@ -184,6 +186,7 @@ onBeforeMount(() => {
const urlParams = route.query[$props.searchUrl]; const urlParams = route.query[$props.searchUrl];
hasParams.value = urlParams && Object.keys(urlParams).length !== 0; hasParams.value = urlParams && Object.keys(urlParams).length !== 0;
}); });
onMounted(async () => { onMounted(async () => {
if ($props.isEditable) document.addEventListener('click', clickHandler); if ($props.isEditable) document.addEventListener('click', clickHandler);
mode.value = mode.value =
@ -206,6 +209,7 @@ onMounted(async () => {
}; };
} }
}); });
onUnmounted(async () => { onUnmounted(async () => {
if ($props.isEditable) document.removeEventListener('click', clickHandler); if ($props.isEditable) document.removeEventListener('click', clickHandler);
}); });
@ -216,6 +220,16 @@ watch(
{ immediate: true }, { immediate: true },
); );
defineExpose({
create: createForm,
reload,
redirect: redirectFn,
selected,
CrudModelRef,
params,
tableRef,
});
function splitColumns(columns) { function splitColumns(columns) {
splittedColumns.value = { splittedColumns.value = {
columns: [], columns: [],
@ -281,17 +295,6 @@ function columnName(col) {
return name; return name;
} }
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
defineExpose({
create: createForm,
reload,
redirect: redirectFn,
selected,
CrudModelRef,
params,
tableRef,
});
function handleOnDataSaved(_) { function handleOnDataSaved(_) {
if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value }); if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value });
else $props.create.onDataSaved(_); else $props.create.onDataSaved(_);
@ -512,6 +515,7 @@ function getCheckboxIcon(value) {
return 'indeterminate_check_box'; return 'indeterminate_check_box';
} }
} }
function getToggleIcon(value) { function getToggleIcon(value) {
if (value === null) return 'help_outline'; if (value === null) return 'help_outline';
return value ? 'toggle_on' : 'toggle_off'; return value ? 'toggle_on' : 'toggle_off';
@ -679,7 +683,8 @@ const checkbox = ref(null);
}" }"
:class="[ :class="[
col.columnClass, col.columnClass,
'body-cell no-margin no-padding text-center', 'body-cell no-margin no-padding',
getColAlign(col),
]" ]"
:data-row-index="rowIndex" :data-row-index="rowIndex"
:data-col-field="col?.name" :data-col-field="col?.name"

View File

@ -1,7 +1,9 @@
export function getColAlign(col) { export function getColAlign(col) {
let align; let align;
switch (col.component) { switch (col.component) {
case 'select':
align = 'left';
break;
case 'number': case 'number':
align = 'right'; align = 'right';
break; break;

View File

@ -640,6 +640,7 @@ onMounted(() => {
:table-height="$props.tableHeight ?? '84vh'" :table-height="$props.tableHeight ?? '84vh'"
auto-load auto-load
footer footer
data-cy="entry-buys"
> >
<template #column-hex="{ row }"> <template #column-hex="{ row }">
<VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" /> <VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" />

View File

@ -124,7 +124,7 @@ async function recalculateRates(entity) {
async function cloneEntry() { async function cloneEntry() {
try { try {
const response = await axios.post(`Entries/${entityId.value}/cloneEntry`); const response = await axios.post(`Entries/${entityId.value}/cloneEntry`);
push({ path: `/entry/${response.data[0].vNewEntryFk}` }); push({ path: `/entry/${response.data}` });
showNotification('positive', 'Entry cloned'); showNotification('positive', 'Entry cloned');
} catch (error) { } catch (error) {
showNotification('negative', 'Failed to clone entry'); showNotification('negative', 'Failed to clone entry');
@ -174,13 +174,7 @@ async function deleteEntry() {
<QItem v-ripple clickable @click="cloneEntry(entity)" data-cy="clone-entry"> <QItem v-ripple clickable @click="cloneEntry(entity)" data-cy="clone-entry">
<QItemSection>{{ t('Clone') }}</QItemSection> <QItemSection>{{ t('Clone') }}</QItemSection>
</QItem> </QItem>
<QItem <QItem v-ripple clickable @click="deleteEntry(entity)" data-cy="delete-entry">
v-ripple
clickable
@click="deleteEntry(entity)"
data-cy="delete-entry"
v-if="entity?.travelFk"
>
<QItemSection>{{ t('Delete') }}</QItemSection> <QItemSection>{{ t('Delete') }}</QItemSection>
</QItem> </QItem>
</template> </template>
@ -293,4 +287,7 @@ es:
Failed to recalculate rates: No se pudieron recalcular las tarifas Failed to recalculate rates: No se pudieron recalcular las tarifas
Failed to clone entry: No se pudo clonar la entrada Failed to clone entry: No se pudo clonar la entrada
Failed to delete entry: No se pudo eliminar la entrada Failed to delete entry: No se pudo eliminar la entrada
Recalculate rates: Recalcular tarifas
Clone: Clonar
Delete: Eliminar
</i18n> </i18n>

View File

@ -51,6 +51,7 @@ onMounted(async () => {
:url="`Entries/${entityId}/getEntry`" :url="`Entries/${entityId}/getEntry`"
@on-fetch="(data) => setEntryData(data)" @on-fetch="(data) => setEntryData(data)"
data-key="EntrySummary" data-key="EntrySummary"
data-cy="entry-summary"
> >
<template #header-left> <template #header-left>
<VnToSummary <VnToSummary

View File

@ -44,7 +44,6 @@ const entryQueryFilter = {
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'center',
label: 'Ex', label: 'Ex',
toolTip: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), toolTip: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'),
name: 'isExcludedFromAvailable', name: 'isExcludedFromAvailable',
@ -52,7 +51,6 @@ const columns = computed(() => [
width: '35px', width: '35px',
}, },
{ {
align: 'center',
label: 'Pe', label: 'Pe',
toolTip: t('entry.list.tableVisibleColumns.isOrdered'), toolTip: t('entry.list.tableVisibleColumns.isOrdered'),
name: 'isOrdered', name: 'isOrdered',
@ -60,7 +58,6 @@ const columns = computed(() => [
width: '35px', width: '35px',
}, },
{ {
align: 'center',
label: 'Le', label: 'Le',
toolTip: t('entry.list.tableVisibleColumns.isConfirmed'), toolTip: t('entry.list.tableVisibleColumns.isConfirmed'),
name: 'isConfirmed', name: 'isConfirmed',
@ -68,7 +65,6 @@ const columns = computed(() => [
width: '35px', width: '35px',
}, },
{ {
align: 'center',
label: 'Re', label: 'Re',
toolTip: t('entry.list.tableVisibleColumns.isReceived'), toolTip: t('entry.list.tableVisibleColumns.isReceived'),
name: 'isReceived', name: 'isReceived',
@ -76,7 +72,6 @@ const columns = computed(() => [
width: '35px', width: '35px',
}, },
{ {
align: 'center',
label: t('entry.list.tableVisibleColumns.landed'), label: t('entry.list.tableVisibleColumns.landed'),
name: 'landed', name: 'landed',
component: 'date', component: 'date',
@ -87,16 +82,15 @@ const columns = computed(() => [
width: '105px', width: '105px',
}, },
{ {
align: 'right',
label: t('globals.id'), label: t('globals.id'),
name: 'id', name: 'id',
isId: true, isId: true,
component: 'number',
chip: { chip: {
condition: () => true, condition: () => true,
}, },
}, },
{ {
align: 'left',
label: t('entry.list.tableVisibleColumns.supplierFk'), label: t('entry.list.tableVisibleColumns.supplierFk'),
name: 'supplierFk', name: 'supplierFk',
create: true, create: true,

View File

@ -7,8 +7,8 @@ describe('Entry', () => {
it('Filter deleted entries and other fields', () => { it('Filter deleted entries and other fields', () => {
createEntry(); createEntry();
cy.get('.q-notification__message').eq(0).should('have.text', 'Data created'); cy.get('.q-notification__message').eq(0).should('have.text', 'Data created');
cy.waitForElement('[data-cy="entry-buys"]');
deleteEntry(); deleteEntry();
cy.typeSearchbar('{enter}'); cy.typeSearchbar('{enter}');
cy.get('span[title="Date"]').click().click(); cy.get('span[title="Date"]').click().click();
@ -31,28 +31,35 @@ describe('Entry', () => {
it('Clone entry and recalculate rates', () => { it('Clone entry and recalculate rates', () => {
createEntry(); createEntry();
cy.get('.q-notification__message').eq(0).should('have.text', 'Data created');
cy.url().then((perviousUrl) => { cy.waitForElement('[data-cy="entry-buys"]');
cy.log('URL antes de clonar:', perviousUrl);
cy.url().then((previousUrl) => {
cy.get('[data-cy="descriptor-more-opts"]').click(); cy.get('[data-cy="descriptor-more-opts"]').click();
cy.get('div[data-cy="clone-entry"]').click(); cy.get('div[data-cy="clone-entry"]').should('be.visible').click();
cy.url().then((newUrl) => { cy.get('.q-notification__message').eq(1).should('have.text', 'Entry cloned');
expect(perviousUrl).not.to.eq(newUrl);
cy.url()
.should('not.eq', previousUrl)
.then(() => {
cy.waitForElement('[data-cy="entry-buys"]');
cy.get('[data-cy="descriptor-more-opts"]').click(); cy.get('[data-cy="descriptor-more-opts"]').click();
cy.get('div[data-cy="recalculate-rates"]').click(); cy.get('div[data-cy="recalculate-rates"]').click();
cy.get('.q-notification__message') cy.get('.q-notification__message')
.eq(1) .eq(2)
.should('have.text', 'Entry prices recalculated'); .should('have.text', 'Entry prices recalculated');
cy.get('[data-cy="descriptor-more-opts"]').click();
deleteEntry(); deleteEntry();
cy.visit(perviousUrl); cy.log(previousUrl);
cy.visit(previousUrl);
cy.waitForElement('[data-cy="entry-buys"]');
deleteEntry(); deleteEntry();
}); });
}); });
@ -174,13 +181,14 @@ describe('Entry', () => {
function goToEntryBuys() { function goToEntryBuys() {
const entryBuySelector = 'a[data-cy="EntryBuys-menu-item"]'; const entryBuySelector = 'a[data-cy="EntryBuys-menu-item"]';
cy.get(entryBuySelector).should('be.visible'); cy.get(entryBuySelector).should('be.visible');
cy.get(entryBuySelector).click(); cy.waitForElement('[data-cy="entry-buys"]');
cy.get(entryBuySelector).click(); cy.get(entryBuySelector).click();
} }
function deleteEntry() { function deleteEntry() {
cy.get('[data-cy="descriptor-more-opts"]').click(); cy.get('[data-cy="descriptor-more-opts"]').click();
cy.get('div[data-cy="delete-entry"]').click(); cy.waitForElement('div[data-cy="delete-entry"]');
cy.get('div[data-cy="delete-entry"]').should('be.visible').click();
cy.url().should('include', 'list'); cy.url().should('include', 'list');
} }

View File

@ -87,36 +87,55 @@ Cypress.Commands.add('getValue', (selector) => {
}); });
// Fill Inputs // Fill Inputs
Cypress.Commands.add('selectOption', (selector, option, timeout = 5000) => { Cypress.Commands.add('selectOption', (selector, option, timeout = 2500) => {
cy.waitForElement(selector, timeout); cy.waitForElement(selector, timeout);
cy.get(selector).click();
cy.get(selector).invoke('data', 'url').as('dataUrl');
cy.get(selector)
.clear()
.type(option)
.then(() => {
cy.get('.q-menu', { timeout })
.should('be.visible') // Asegurarse de que el menú está visible
.and('exist') // Verificar que el menú existe
.then(() => {
cy.get('@dataUrl').then((url) => {
if (url) {
// Esperar a que el menú no esté visible (desaparezca)
cy.get('.q-menu').should('not.be.visible');
// Ahora esperar a que el menú vuelva a aparecer
cy.get('.q-menu').should('be.visible').and('exist');
}
});
});
});
// Finalmente, seleccionar la opción deseada cy.get(selector, { timeout })
cy.get('.q-menu:visible') // Asegurarse de que estamos dentro del menú visible .should('exist')
.find('.q-item') // Encontrar los elementos de las opciones .should('be.visible')
.contains(option) // Verificar que existe una opción que contenga el texto deseado .click()
.click(); // Hacer clic en la opción .then(($el) => {
cy.wrap($el.is('input') ? $el : $el.find('input'))
.invoke('attr', 'aria-controls')
.then((ariaControl) => selectItem(selector, option, ariaControl));
});
}); });
function selectItem(selector, option, ariaControl, hasWrite = true) {
if (!hasWrite) cy.wait(100);
getItems(ariaControl).then((items) => {
const matchingItem = items
.toArray()
.find((item) => item.innerText.includes(option));
if (matchingItem) return cy.wrap(matchingItem).click();
if (hasWrite) cy.get(selector).clear().type(option, { delay: 0 });
return selectItem(selector, option, ariaControl, false);
});
}
function getItems(ariaControl, startTime = Cypress._.now(), timeout = 2500) {
// Se intenta obtener la lista de opciones del desplegable de manera recursiva
return cy
.get('#' + ariaControl, { timeout })
.should('exist')
.find('.q-item')
.should('exist')
.then(($items) => {
if (!$items?.length || $items.first().text().trim() === '') {
if (Cypress._.now() - startTime > timeout) {
throw new Error(
`getItems: Tiempo de espera (${timeout}ms) excedido.`,
);
}
return getItems(ariaControl, startTime, timeout);
}
return cy.wrap($items);
});
}
Cypress.Commands.add('countSelectOptions', (selector, option) => { Cypress.Commands.add('countSelectOptions', (selector, option) => {
cy.waitForElement(selector); cy.waitForElement(selector);
cy.get(selector).click({ force: true }); cy.get(selector).click({ force: true });