diff --git a/package.json b/package.json index ccff022cf..19b4c7a6f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "test:e2e:summary": "bash ./test/cypress/summary.sh", "test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test:front": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", "test:front:ci": "vitest run", "commitlint": "commitlint --edit", "prepare": "npx husky install", @@ -27,6 +29,8 @@ "docs:preview": "vitepress preview docs" }, "dependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.20.0", "@quasar/cli": "^2.4.1", "@quasar/extras": "^1.16.16", "axios": "^1.4.0", @@ -40,8 +44,6 @@ "validator": "^13.9.0", "vue": "^3.5.13", "vue-i18n": "^9.4.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "^9.20.0", "vue-router": "^4.2.5" }, "devDependencies": { @@ -54,6 +56,7 @@ "@quasar/app-vite": "^2.0.8", "@quasar/quasar-app-extension-qcalendar": "^4.0.2", "@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0", + "@vitest/ui": "3.1.1", "@vue/compiler-sfc": "^3.5.13", "@vue/test-utils": "^2.4.4", "autoprefixer": "^10.4.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35b1a9ea8..02d82f325 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,7 +75,10 @@ devDependencies: version: 4.1.2 '@quasar/quasar-app-extension-testing-unit-vitest': specifier: ^0.4.0 - version: 0.4.0(@vue/test-utils@2.4.6)(quasar@2.18.1)(typescript@5.8.3)(vite@6.2.5)(vitest@3.1.1)(vue@3.5.13) + version: 0.4.0(@vitest/ui@3.1.1)(@vue/test-utils@2.4.6)(quasar@2.18.1)(typescript@5.8.3)(vite@6.2.5)(vitest@3.1.1)(vue@3.5.13) + '@vitest/ui': + specifier: 3.1.1 + version: 3.1.1(vitest@3.1.1) '@vue/compiler-sfc': specifier: ^3.5.13 version: 3.5.13 @@ -132,7 +135,7 @@ devDependencies: version: 1.86.3 vitest: specifier: ^3.0.3 - version: 3.1.1(@types/node@22.14.0)(sass@1.86.3) + version: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(sass@1.86.3) xunit-viewer: specifier: ^10.6.1 version: 10.6.1(@babel/runtime@7.27.0)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.5)(codemirror@6.0.1)(react-dom@19.1.0)(react@19.1.0) @@ -1139,6 +1142,10 @@ packages: config-chain: 1.1.13 dev: false + /@polka/url@1.0.0-next.28: + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + dev: true + /@quasar/app-vite@2.2.0(@types/node@22.14.0)(eslint@9.24.0)(pinia@2.3.1)(quasar@2.18.1)(sass@1.86.3)(typescript@5.8.3)(vue-router@4.5.0)(vue@3.5.13): resolution: {integrity: sha512-MvCfJrCbxUYvoGaK5jPq0h0hjO8mbxYOWngf+dIKrxhwb+1h5ERh6aVYEUuCtMIwTMEVfPkCez4DIfZIoReuDw==} engines: {node: ^30 || ^28 || ^26 || ^24 || ^22 || ^20 || ^18, npm: '>= 6.14.12', yarn: '>= 1.17.3'} @@ -1262,7 +1269,7 @@ packages: '@quasar/quasar-ui-qcalendar': 4.1.2 dev: true - /@quasar/quasar-app-extension-testing-unit-vitest@0.4.0(@vue/test-utils@2.4.6)(quasar@2.18.1)(typescript@5.8.3)(vite@6.2.5)(vitest@3.1.1)(vue@3.5.13): + /@quasar/quasar-app-extension-testing-unit-vitest@0.4.0(@vitest/ui@3.1.1)(@vue/test-utils@2.4.6)(quasar@2.18.1)(typescript@5.8.3)(vite@6.2.5)(vitest@3.1.1)(vue@3.5.13): resolution: {integrity: sha512-eyzdUdmZiCueNS+5nedjMmzdbpCetSrtdGIwW6KplW1dTzRbLiNvYUjpBOxQGmJCgEhWy9zuswJ7MZ/bTql24Q==} engines: {node: '>= 12.22.1', npm: '>= 6.14.12', yarn: '>= 1.17.3'} peerDependencies: @@ -1275,13 +1282,14 @@ packages: '@vitest/ui': optional: true dependencies: + '@vitest/ui': 3.1.1(vitest@3.1.1) '@vue/test-utils': 2.4.6 happy-dom: 11.2.0 lodash-es: 4.17.21 quasar: 2.18.1 vite-jsconfig-paths: 2.0.1(vite@6.2.5) vite-tsconfig-paths: 4.3.2(typescript@5.8.3)(vite@6.2.5) - vitest: 3.1.1(@types/node@22.14.0)(sass@1.86.3) + vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(sass@1.86.3) vue: 3.5.13(typescript@5.8.3) transitivePeerDependencies: - supports-color @@ -1813,6 +1821,21 @@ packages: tinyspy: 3.0.2 dev: true + /@vitest/ui@3.1.1(vitest@3.1.1): + resolution: {integrity: sha512-2HpiRIYg3dlvAJBV9RtsVswFgUSJK4Sv7QhpxoP0eBGkYwzGIKP34PjaV00AULQi9Ovl6LGyZfsetxDWY5BQdQ==} + peerDependencies: + vitest: 3.1.1 + dependencies: + '@vitest/utils': 3.1.1 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 2.0.3 + sirv: 3.0.1 + tinyglobby: 0.2.12 + tinyrainbow: 2.0.0 + vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(sass@1.86.3) + dev: true + /@vitest/utils@3.1.1: resolution: {integrity: sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==} dependencies: @@ -4021,6 +4044,10 @@ packages: picomatch: 4.0.2 dev: true + /fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + dev: true + /figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -5588,6 +5615,11 @@ packages: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} dev: false + /mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + dev: true + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -6964,6 +6996,15 @@ packages: engines: {node: '>=14'} dev: true + /sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} + dependencies: + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.1 + totalist: 3.0.1 + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -7364,6 +7405,11 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + /totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + dev: true + /tough-cookie@5.1.2: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} @@ -7772,7 +7818,7 @@ packages: fsevents: 2.3.3 dev: true - /vitest@3.1.1(@types/node@22.14.0)(sass@1.86.3): + /vitest@3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(sass@1.86.3): resolution: {integrity: sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true @@ -7807,6 +7853,7 @@ packages: '@vitest/runner': 3.1.1 '@vitest/snapshot': 3.1.1 '@vitest/spy': 3.1.1 + '@vitest/ui': 3.1.1(vitest@3.1.1) '@vitest/utils': 3.1.1 chai: 5.2.0 debug: 4.4.0(supports-color@8.1.1) diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index c4d9a4149..1fec1e6c9 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -13,13 +13,12 @@ import VnConfirm from './ui/VnConfirm.vue'; import { tMobile } from 'src/composables/tMobile'; import { useArrayData } from 'src/composables/useArrayData'; import { getDifferences, getUpdatedValues } from 'src/filters'; - const { push } = useRouter(); const quasar = useQuasar(); const state = useState(); const stateStore = useStateStore(); const { t } = useI18n(); -const { validate } = useValidator(); +const { validate, validations } = useValidator(); const { notify } = useNotify(); const route = useRoute(); const myForm = ref(null); @@ -119,7 +118,7 @@ const defaultButtons = computed(() => ({ color: 'primary', icon: 'save', label: 'globals.save', - click: async () => await save(), + click: async (evt) => submitForm(evt), type: 'submit', }, reset: { @@ -132,6 +131,13 @@ const defaultButtons = computed(() => ({ ...$props.defaultButtons, })); +const submitForm = async (evt) => { + const isFormValid = await myForm.value.validate(); + if (isFormValid) { + await save(evt); + } +}; + onMounted(async () => { nextTick(() => (componentIsRendered.value = true)); @@ -227,10 +233,9 @@ async function save() { const method = $props.urlCreate ? 'post' : 'patch'; const url = $props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url; - let response; - - if ($props.saveFn) response = await $props.saveFn(body); - else response = await axios[method](url, body); + const response = await Promise.resolve( + $props.saveFn ? $props.saveFn(body) : axios[method](url, body), + ); if ($props.urlCreate) notify('globals.dataCreated', 'positive'); @@ -307,11 +312,13 @@ async function onKeyup(evt) { selectionStart = selectionEnd = selectionStart + 1; return; } - await save(); + await myForm.value.submit(evt); } } defineExpose({ + submitForm, + myForm, save, isLoading, hasChanges, @@ -325,7 +332,7 @@ defineExpose({ diff --git a/src/components/FormModelPopup.vue b/src/components/FormModelPopup.vue index 85943e91e..34aec96d8 100644 --- a/src/components/FormModelPopup.vue +++ b/src/components/FormModelPopup.vue @@ -41,9 +41,12 @@ const onDataSaved = async (formData, requestResponse) => { emit('onDataSaved', formData, requestResponse); }; -const onClick = async (saveAndContinue) => { +const onClick = async (saveAndContinue = showSaveAndContinueBtn) => { + await formModelRef.value.myForm.validate(true); isSaveAndContinue.value = saveAndContinue; - await formModelRef.value.save(); + if (formModelRef.value) { + await formModelRef.value.submitForm(); + } }; defineExpose({ @@ -59,16 +62,23 @@ defineExpose({ ref="formModelRef" :observe-form-changes="false" :default-actions="false" + @submit="onClick" v-bind="$attrs" @on-data-saved="onDataSaved" + :prevent-submit="false" > - + {{ title }} {{ subtitle }} - + diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 1c85cbd3d..c41f711de 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -1045,7 +1045,7 @@ const rowCtrlClickFunction = computed(() => { :model="$attrs['data-key'] + 'Create'" @on-data-saved="(_, res) => createForm.onDataSaved(res)" > - + { :key="column.name" :name="`column-create-${column.name}`" :data="data" + :validations="validations" :column-name="column.name" :label="column.label" > diff --git a/src/components/common/VnJsonValue.vue b/src/components/common/VnJsonValue.vue index a2e858d0d..ba37ab1d6 100644 --- a/src/components/common/VnJsonValue.vue +++ b/src/components/common/VnJsonValue.vue @@ -1,6 +1,6 @@ - + { const totalNewPrice = computed(() => { return rows.value.reduce( (acc, item) => acc + item.component.newPrice * item.quantity, - 0 + 0, ); }); @@ -210,18 +210,18 @@ onMounted(async () => { flat > - - + + {{ row.itemFk }} - + {{ row.item.name }} - + diff --git a/src/pages/Ticket/Card/TicketCreateTracking.vue b/src/pages/Ticket/Card/TicketCreateTracking.vue deleted file mode 100644 index 5c1e916f2..000000000 --- a/src/pages/Ticket/Card/TicketCreateTracking.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - (statesOptions = data)" - /> - emit('onRequestCreated')" - > - - - - - - - - - - - es: - Create tracking: Crear estado - diff --git a/src/pages/Ticket/Card/TicketTracking.vue b/src/pages/Ticket/Card/TicketTracking.vue index 00610de44..06171366d 100644 --- a/src/pages/Ticket/Card/TicketTracking.vue +++ b/src/pages/Ticket/Card/TicketTracking.vue @@ -1,27 +1,23 @@ - - - - - - - - {{ row.user?.name }} - - - - - - - - - - - - + + - - {{ t('tracking.addState') }} - - - + + + + + {{ row.user.name }} + + + + + + es: + Create tracking: Crear estado + diff --git a/src/pages/Ticket/Card/TicketVolume.vue b/src/pages/Ticket/Card/TicketVolume.vue index db78094cf..690ae9063 100644 --- a/src/pages/Ticket/Card/TicketVolume.vue +++ b/src/pages/Ticket/Card/TicketVolume.vue @@ -134,7 +134,7 @@ onMounted(() => (stateStore.rightDrawer = true)); auto-load > - + {{ row.itemFk }} diff --git a/src/pages/Worker/Card/WorkerCalendarFilter.vue b/src/pages/Worker/Card/WorkerCalendarFilter.vue index f0e2d758a..32edaa6e9 100644 --- a/src/pages/Worker/Card/WorkerCalendarFilter.vue +++ b/src/pages/Worker/Card/WorkerCalendarFilter.vue @@ -166,50 +166,44 @@ const yearList = ref(generateYears()); }} - - - - - - - - - - - # {{ scope.opt?.businessFk }} - - {{ toDateFormat(scope.opt?.started) }} - - {{ - scope.opt?.ended - ? toDateFormat(scope.opt?.ended) - : 'Indef.' - }} - - - - - - - - + + + + + + + # {{ scope.opt?.businessFk }} + + {{ toDateFormat(scope.opt?.started) }} - + {{ + scope.opt?.ended + ? toDateFormat(scope.opt?.ended) + : 'Indef.' + }} + + + + + + route.params.id); @@ -84,6 +85,7 @@ function reloadData() { initialData.value.deviceProductionFk = null; initialData.value.simFk = null; tableRef.value.reload(); + getAvailablePdaRef.value.fetch(); } async function fetchDocuware() { @@ -135,6 +137,7 @@ async function deallocatePDA(deviceProductionFk) { ); delete tableRef.value.CrudModelRef.formData[index]; notify(t('PDA deallocated'), 'positive'); + await getAvailablePdaRef.value.fetch(); } function isSigned(row) { @@ -144,6 +147,7 @@ function isSigned(row) { (deviceProductions = data)" auto-load @@ -234,7 +238,7 @@ function isSigned(row) { data-cy="workerPda-download" > - {{ t('worker.pda.download') }} + {{ t('globals.downloadPdf') }} @@ -307,4 +311,5 @@ es: This PDA is already assigned to another user: Este PDA ya está asignado a otro usuario Are you sure you want to send it?: ¿Seguro que quieres enviarlo? Sign PDA: Firmar PDA + PDF sended to signed: PDF enviado para firmar diff --git a/test/cypress/docker/find/find-imports.js b/test/cypress/docker/find/find-imports.js index 39c3ac3eb..622049c9f 100644 --- a/test/cypress/docker/find/find-imports.js +++ b/test/cypress/docker/find/find-imports.js @@ -44,7 +44,7 @@ export async function findImports(targetFile, visited = new Set(), identation = ]; } - return getUniques(fullTree); // Remove duplicates + return getUniques([...fullTree, targetFile]); // Remove duplicates } function getUniques(array) { diff --git a/test/cypress/docker/find/find.js b/test/cypress/docker/find/find.js index b89aab230..4f8063c86 100644 --- a/test/cypress/docker/find/find.js +++ b/test/cypress/docker/find/find.js @@ -25,7 +25,7 @@ async function getChangedModules() { if (change.startsWith(E2E_PATH)) changedArray.push(change); changedModules = new Set(changedArray); } - return [...changedModules].join('\n'); + return cleanSpecs(changedModules).join('\n'); } getChangedModules() @@ -34,3 +34,20 @@ getChangedModules() console.error(e); process.exit(1); }); + +function cleanSpecs(changedModules) { + let specifics = []; + const modules = []; + for (const changed of changedModules) { + if (changed.endsWith('*.spec.js')) { + modules.push(changed); + continue; + } + specifics.push(changed); + } + specifics = specifics.filter( + (spec) => !modules.some((module) => spec.startsWith(module.split('**')[0])), + ); + + return [...modules, ...specifics]; +} diff --git a/test/cypress/integration/item/itemSummary.spec.js b/test/cypress/integration/item/itemSummary.spec.js index ad8267ecf..8d67c8e3c 100644 --- a/test/cypress/integration/item/itemSummary.spec.js +++ b/test/cypress/integration/item/itemSummary.spec.js @@ -19,6 +19,7 @@ describe('Item summary', () => { cy.get('.q-menu > .q-list > :nth-child(1) > .q-item__section').click(); cy.dataCy('regularizeStockInput').type('10'); cy.dataCy('Warehouse_select').type('Warehouse One{enter}'); + cy.dataCy('FormModelPopup_save').click(); cy.checkNotification('Data created'); }); }); diff --git a/test/cypress/integration/ticket/ticketBasicData.spec.js b/test/cypress/integration/ticket/ticketBasicData.spec.js new file mode 100644 index 000000000..443e9569b --- /dev/null +++ b/test/cypress/integration/ticket/ticketBasicData.spec.js @@ -0,0 +1,46 @@ +/// +describe('TicketBasicData', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/basic-data'); + }); + + it('Should redirect to customer basic data', () => { + cy.get('.q-page').should('be.visible'); + cy.get(':nth-child(2) > div > .text-primary').click(); + cy.dataCy('Address_select').click(); + cy.get('.q-btn-group ').find('.q-btn__content > .q-icon').click(); + cy.get( + '[data-cy="CustomerBasicData-menu-item"] > .q-item__section--main', + ).click(); + cy.url().should('include', '/customer/1104/basic-data'); + }); + it.only('stepper', () => { + cy.get('.q-stepper__tab--active').should('have.class', 'q-stepper__tab--active'); + + cy.get('.q-stepper__nav > .q-btn--standard').click(); + cy.get('.q-stepper__tab--done').should('have.class', 'q-stepper__tab--done'); + cy.get('.q-stepper__tab--active').should('have.class', 'q-stepper__tab--active'); + cy.get('tr:nth-child(1)>:nth-child(1)>span').should('have.class', 'link').click(); + cy.dataCy('ItemDescriptor').should('exist'); + + cy.get('.q-drawer__content > :nth-child(1)').each(() => { + cy.get('span').should('contain.text', 'Price: €'); + cy.get('span').should('contain.text', 'New price: €'); + cy.get('span').should('contain.text', 'Difference: €'); + }); + cy.get( + ':nth-child(3) > .q-radio > .q-radio__inner > .q-radio__bg > .q-radio__check', + ).should('have.class', 'q-radio__check'); + cy.get( + '.q-stepper__step-inner > .q-drawer-container > .q-drawer > .q-drawer__content', + ).click(); + cy.get(':nth-child(2) > :nth-child(1) > .text-weight-bold').click(); + cy.get(':nth-child(3) > .q-radio > .q-radio__inner').should( + 'have.class', + 'q-radio__inner--truthy', + ); + cy.get('.q-drawer__content > :nth-child(2)').click(); + }); +}); diff --git a/test/cypress/integration/ticket/ticketComponents.spec.js b/test/cypress/integration/ticket/ticketComponents.spec.js new file mode 100644 index 000000000..23dbf8bcd --- /dev/null +++ b/test/cypress/integration/ticket/ticketComponents.spec.js @@ -0,0 +1,30 @@ +/// + +describe('TicketComponents', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/1/components'); + }); + it('Should load layout', () => { + cy.get('.q-page').should('be.visible'); + cy.validateScrollContent([ + { row: 2, col: 2, text: 'Base to commission: €799.20' }, + { row: 2, col: 3, text: 'Total without VAT: €807.20' }, + { row: 3, col: 2, text: 'valor de compra: €425.000' }, + { row: 3, col: 4, text: 'maná auto: €7.998' }, + { row: 4, col: 2, text: 'Price: €5.00' }, + { row: 4, col: 3, text: 'Bonus: €1.00' }, + { row: 4, col: 5, text: 'Packages: 6' }, + { row: 4, col: 4, text: 'Zone: Zone pickup A ' }, + { row: 5, col: 2, text: 'Total price: €16.00' }, + ]); + cy.get(':nth-child(4) > .link').click(); + + cy.dataCy('ZoneDescriptor').should('exist'); + cy.getRowCol('total').should('have.text', '€250.000€247.000€4.970'); + cy.getRowCol('import').should('have.text', '€50.000€49.400€0.994'); + cy.getRowCol('components').should('have.text', 'valor de compramargenmaná auto'); + cy.getRowCol('serie').should('have.text', 'costeempresacartera_comercial'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 5613a5854..e18025319 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -1,7 +1,5 @@ /// describe('TicketList', () => { - const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; - beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); @@ -11,7 +9,7 @@ describe('TicketList', () => { const searchResults = (search) => { if (search) cy.typeSearchbar().type(search); cy.dataCy('vn-searchbar').find('input').type('{enter}'); - cy.get(firstRow).should('exist'); + cy.getRow().should('exist'); }; it('should search results', () => { @@ -24,13 +22,13 @@ describe('TicketList', () => { cy.window().then((win) => { cy.stub(win, 'open').as('windowOpen'); }); - cy.get(firstRow).should('be.visible').find('.q-btn:first').click(); + cy.getRow().should('be.visible').find('.q-btn:first').click(); cy.get('@windowOpen').should('be.calledWithMatch', /\/ticket\/\d+\/sale/); }); it('should open ticket summary', () => { searchResults(); - cy.get(firstRow).find('.q-btn:last').click(); + cy.getRow().find('.q-btn:last').click(); cy.get('.summaryHeader').should('exist'); cy.get('.summaryBody').should('exist'); }); @@ -43,8 +41,9 @@ describe('TicketList', () => { cy.dataCy('Customer ID_input').clear('1'); cy.dataCy('Customer ID_input').type('1101{enter}'); - cy.get('[data-cy="vnTableCreateBtn"] > .q-btn__content > .q-icon').click(); - cy.waitSpinner(); + cy.intercept('GET', /\/api\/Clients\?filter/).as('clientFilter'); + cy.vnTableCreateBtn(); + cy.wait('@clientFilter'); cy.dataCy('Customer_select').should('have.value', 'Bruce Wayne'); cy.dataCy('Address_select').click(); @@ -52,8 +51,7 @@ describe('TicketList', () => { cy.dataCy('Address_select').should('have.value', 'Bruce Wayne'); }); it('Client list create new ticket', () => { - cy.dataCy('vnTableCreateBtn').should('exist'); - cy.dataCy('vnTableCreateBtn').click(); + cy.vnTableCreateBtn(); const data = { Customer: { val: 1, type: 'select' }, Warehouse: { val: 'Warehouse One', type: 'select' }, diff --git a/test/cypress/integration/ticket/ticketNotes.spec.js b/test/cypress/integration/ticket/ticketNotes.spec.js index 5b44f9e1f..df1ff9137 100644 --- a/test/cypress/integration/ticket/ticketNotes.spec.js +++ b/test/cypress/integration/ticket/ticketNotes.spec.js @@ -19,7 +19,7 @@ describe('TicketNotes', () => { cy.checkNotification('Data saved'); cy.dataCy('ticketNotesRemoveNoteBtn').should('exist'); cy.dataCy('ticketNotesRemoveNoteBtn').click(); - cy.dataCy('VnConfirm_confirm').click(); + cy.confirmVnConfirm(); cy.checkNotification('Data saved'); }); }); diff --git a/test/cypress/integration/ticket/ticketPackage.spec.js b/test/cypress/integration/ticket/ticketPackage.spec.js new file mode 100644 index 000000000..992efd53b --- /dev/null +++ b/test/cypress/integration/ticket/ticketPackage.spec.js @@ -0,0 +1,21 @@ +/// +describe('TicketPackages', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/package'); + }); + + it('Should load layout', () => { + cy.get('.q-page').should('be.visible'); + cy.get('.vn-row > .q-btn > .q-btn__content > .q-icon').click(); + cy.dataCy('Package_select').click(); + cy.get('.q-menu :nth-child(1) >.q-item__section').click(); + cy.dataCy('Quantity_input').clear().type('5'); + cy.saveCrudModel(); + cy.checkNotification('Data saved'); + cy.get('.q-mb-md > .text-primary').click(); + cy.confirmVnConfirm(); + cy.checkNotification('Data saved'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketPictures.spec.js b/test/cypress/integration/ticket/ticketPictures.spec.js new file mode 100644 index 000000000..1165b54bf --- /dev/null +++ b/test/cypress/integration/ticket/ticketPictures.spec.js @@ -0,0 +1,18 @@ +/// +describe('TicketPictures', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/picture'); + }); + it('Should load layout', () => { + cy.get(':nth-child(1) > .q-card > .content').should('be.visible'); + cy.get('.content > .link').should('be.visible').click(); + cy.dataCy('ItemDescriptor').should('exist'); + cy.dataCy('vnLvColor:'); + cy.dataCy('vnLvColor:'); + cy.dataCy('vnLvTallos:'); + cy.get('.q-mt-md').should('be.visible'); + cy.get(':nth-child(1) > .q-card > .img-wrapper').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketRequest.spec.js b/test/cypress/integration/ticket/ticketRequest.spec.js index b9dc509ef..3b237826e 100644 --- a/test/cypress/integration/ticket/ticketRequest.spec.js +++ b/test/cypress/integration/ticket/ticketRequest.spec.js @@ -7,8 +7,7 @@ describe('TicketRequest', () => { }); it('Creates a new request', () => { - cy.dataCy('vnTableCreateBtn').should('exist'); - cy.dataCy('vnTableCreateBtn').click(); + cy.vnTableCreateBtn(); const data = { Description: { val: 'Purchase description' }, Atender: { val: 'buyerNick', type: 'select' }, diff --git a/test/cypress/integration/ticket/ticketSale.spec.js b/test/cypress/integration/ticket/ticketSale.spec.js index 6d84f214c..f433f0d11 100644 --- a/test/cypress/integration/ticket/ticketSale.spec.js +++ b/test/cypress/integration/ticket/ticketSale.spec.js @@ -2,9 +2,9 @@ const firstRow = 'tbody > :nth-child(1)'; describe('TicketSale', () => { - describe('Ticket #23', () => { + describe('#23', () => { beforeEach(() => { - cy.login('developer'); + cy.login('salesBoss'); cy.viewport(1920, 1080); cy.visit('/#/ticket/23/sale'); }); @@ -16,10 +16,12 @@ describe('TicketSale', () => { cy.waitForElement('[data-cy="ticketEditManaProxy"]'); cy.dataCy('ticketEditManaProxy').should('exist'); cy.waitForElement('[data-cy="Price_input"]'); - cy.dataCy('Price_input').clear(); - cy.dataCy('Price_input').type(price); + cy.dataCy('Price_input').clear().type(price); + cy.intercept('POST', /\/api\/Sales\/\d+\/updatePrice/).as('updatePrice'); + cy.dataCy('saveManaBtn').click(); handleVnConfirm(); + cy.wait('@updatePrice').its('response.statusCode').should('eq', 200); cy.get('[data-col-field="price"]') .find('.q-btn > .q-btn__content') @@ -32,10 +34,14 @@ describe('TicketSale', () => { cy.waitForElement('[data-cy="ticketEditManaProxy"]'); cy.dataCy('ticketEditManaProxy').should('exist'); cy.waitForElement('[data-cy="Disc_input"]'); - cy.dataCy('Disc_input').clear(); - cy.dataCy('Disc_input').type(discount); + cy.dataCy('Disc_input').clear().type(discount); + cy.intercept('POST', /\/api\/Tickets\/\d+\/updateDiscount/).as( + 'updateDiscount', + ); + cy.dataCy('saveManaBtn').click(); handleVnConfirm(); + cy.wait('@updateDiscount').its('response.statusCode').should('eq', 204); cy.get('[data-col-field="discount"]') .find('.q-btn > .q-btn__content') @@ -46,6 +52,8 @@ describe('TicketSale', () => { const concept = Math.floor(Math.random() * 100) + 1; cy.waitForElement(firstRow); cy.get('[data-col-field="item"]').click(); + cy.intercept('POST', '**/api').as('postRequest'); + cy.get('.q-menu') .find('[data-cy="undefined_input"]') .type(concept) @@ -58,6 +66,8 @@ describe('TicketSale', () => { const quantity = Math.floor(Math.random() * 100) + 1; cy.waitForElement(firstRow); cy.dataCy('ticketSaleQuantityInput').find('input').clear(); + cy.intercept('POST', '**/api').as('postRequest'); + cy.dataCy('ticketSaleQuantityInput') .find('input') .type(quantity) @@ -71,7 +81,7 @@ describe('TicketSale', () => { .should('have.value', `${quantity}`); }); }); - describe('Ticket to add claim #24', () => { + describe('#24 add claim', () => { beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); @@ -82,15 +92,15 @@ describe('TicketSale', () => { selectFirstRow(); cy.dataCy('ticketSaleMoreActionsDropdown').click(); cy.dataCy('createClaimItem').click(); - cy.dataCy('VnConfirm_confirm').click(); + cy.confirmVnConfirm(); cy.url().should('contain', 'claim/'); // Delete created claim to avoid cluttering the database cy.dataCy('descriptor-more-opts').click(); cy.dataCy('deleteClaim').click(); - cy.dataCy('VnConfirm_confirm').click(); + cy.confirmVnConfirm(); }); }); - describe('Free ticket #31', () => { + describe('#31 free ticket', () => { beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); @@ -129,7 +139,9 @@ describe('TicketSale', () => { cy.dataCy('ticketSaleMoreActionsDropdown').should('be.disabled'); }); - it('should update discount when "Update discount" is clicked', () => { + it.only('should update discount when "Update discount" is clicked', () => { + const discount = Number((Math.random() * 99 + 1).toFixed(2)); + selectFirstRow(); cy.dataCy('ticketSaleMoreActionsDropdown').click(); cy.waitForElement('[data-cy="updateDiscountItem"]'); @@ -137,9 +149,13 @@ describe('TicketSale', () => { cy.dataCy('updateDiscountItem').click(); cy.waitForElement('[data-cy="ticketSaleDiscountInput"]'); cy.dataCy('ticketSaleDiscountInput').find('input').focus(); - cy.dataCy('ticketSaleDiscountInput').find('input').type('10'); + cy.intercept('POST', /\/api\/Tickets\/\d+\/updateDiscount/).as( + 'updateDiscount', + ); + cy.dataCy('ticketSaleDiscountInput').find('input').type(discount); + cy.dataCy('saveManaBtn').click(); - cy.waitForElement('.q-notification__message'); + cy.wait('@updateDiscount').its('response.statusCode').should('eq', 204); cy.checkNotification('Data saved'); cy.dataCy('ticketSaleMoreActionsDropdown').should('be.disabled'); }); @@ -148,7 +164,7 @@ describe('TicketSale', () => { selectFirstRow(); cy.dataCy('ticketSaleMoreActionsDropdown').click(); cy.dataCy('createClaimItem').click(); - cy.dataCy('VnConfirm_confirm').click(); + cy.confirmVnConfirm(); cy.checkNotification('Future ticket date not allowed'); }); @@ -173,7 +189,7 @@ describe('TicketSale', () => { cy.url().should('match', /\/ticket\/31\/log/); }); }); - describe('Ticket to transfer #32', () => { + describe('#32 transfer', () => { beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); @@ -194,9 +210,7 @@ function selectFirstRow() { cy.get(firstRow).find('.q-checkbox__inner').click(); } function handleVnConfirm() { - cy.get('[data-cy="VnConfirm_confirm"]').click(); - cy.waitForElement('.q-notification__message'); + cy.confirmVnConfirm(); - cy.get('.q-notification__message').should('be.visible'); cy.checkNotification('Data saved'); } diff --git a/test/cypress/integration/ticket/ticketSaleTracking.spec.js b/test/cypress/integration/ticket/ticketSaleTracking.spec.js new file mode 100644 index 000000000..9ee9f8824 --- /dev/null +++ b/test/cypress/integration/ticket/ticketSaleTracking.spec.js @@ -0,0 +1,53 @@ +/// +function uncheckedSVG(className, state) { + cy.get(`${className} .q-checkbox__svg`).should( + state === 'checked' ? 'not.have.attr' : 'have.attr', + 'fill', + 'none', + ); +} +function checkedSVG(className, state) { + cy.get(`${className} .q-checkbox__svg> .q-checkbox__truthy`).should( + state === 'checked' ? 'not.have.attr' : 'have.attr', + 'fill', + 'none', + ); +} + +function clickIconAndCloseDialog(n) { + cy.get( + `:nth-child(1) > :nth-child(6) > :nth-child(${n}) > .q-btn__content > .q-icon`, + ).click(); +} + +describe('TicketSaleTracking', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/1/sale-tracking'); + }); + + it('Should load layout', () => { + cy.get('.q-page').should('be.visible'); + // Check checkbox states + uncheckedSVG('.pink', 'checked'); + uncheckedSVG('.cyan', 'checked'); + uncheckedSVG('.warning', 'checked'); + uncheckedSVG('.info', 'checked'); + checkedSVG('.yellow', 'unchecked'); + + cy.get('.q-page').click(); + cy.get( + ':nth-child(1) > :nth-child(6) > :nth-child(2) > .q-btn__content > .q-icon', + ).click(); + cy.get('body').type('{esc}'); + cy.get( + ':nth-child(1) > :nth-child(6) > :nth-child(1) > .q-btn__content > .q-icon', + ).click(); + cy.get( + '.q-dialog__inner > .q-table__container :nth-child(1) > :nth-child(2) .link.q-btn', + ).click(); + + cy.dataCy('WorkerDescriptor').should('exist'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketService.spec.js b/test/cypress/integration/ticket/ticketService.spec.js new file mode 100644 index 000000000..5bf8e2aab --- /dev/null +++ b/test/cypress/integration/ticket/ticketService.spec.js @@ -0,0 +1,23 @@ +/// +describe('TicketService', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/service'); + }); + + it('Add and remove service', () => { + cy.get('.q-page').should('be.visible'); + cy.addBtnClick(); + cy.dataCy('Description_icon').click(); + cy.dataCy('Description_input').clear().type('test'); + cy.saveFormModel(); + cy.selectOption('[data-cy="Description_select"]', 'test'); + + cy.dataCy('Quantity_input').clear().type('1'); + cy.dataCy('Price_input').clear().type('2'); + cy.saveCrudModel(); + cy.checkNotification('Data saved'); + cy.get(':nth-child(5) > .q-icon').click(); + }); +}); diff --git a/test/cypress/integration/ticket/ticketSms.spec.js b/test/cypress/integration/ticket/ticketSms.spec.js new file mode 100644 index 000000000..feafb2157 --- /dev/null +++ b/test/cypress/integration/ticket/ticketSms.spec.js @@ -0,0 +1,22 @@ +/// +describe('TicketSms', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/32/sms'); + }); + + it('Should load layout', () => { + cy.get('.q-page').should('be.visible'); + cy.get('.q-infinite-scroll > :nth-child(1)').should( + 'contain.text', + '0004 444444444Lorem ipsum dolor sit amet, consectetur adipiscing elit.2001-01-01 00:00:00OK', + ); + cy.get( + ':nth-child(1) > .q-item > .q-item__section--top > .column > .q-avatar', + ).should('be.visible'); + cy.get( + ':nth-child(1) > .q-item > .q-item__section--side.justify-center > .center > .q-chip > .q-chip__content', + ).should('have.class', 'q-chip__content'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketTracking.spec.js b/test/cypress/integration/ticket/ticketTracking.spec.js new file mode 100644 index 000000000..f351ee0a1 --- /dev/null +++ b/test/cypress/integration/ticket/ticketTracking.spec.js @@ -0,0 +1,25 @@ +/// +describe('Ticket tracking', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/31/tracking'); + }); + + it('Add new tracking', () => { + cy.get('.q-page').should('be.visible'); + + cy.getRowCol('worker').find('span').should('have.class', 'link').click(); + cy.dataCy('WorkerDescriptor').should('exist'); + cy.vnTableCreateBtn(); + cy.selectOption('.q-field--float [data-cy="State_select"]', 'OK').click(); + cy.saveFormModel(); + cy.get( + ':last-child > [data-col-field="state"] > [data-cy="vnTableCell_state"]', + ).should('have.text', 'OK'); + cy.get(':last-child > [data-col-field="worker"]').should( + 'have.text', + 'developer ', + ); + }); +}); diff --git a/test/cypress/integration/ticket/ticketVolume.spec.js b/test/cypress/integration/ticket/ticketVolume.spec.js new file mode 100644 index 000000000..59ff6dcb2 --- /dev/null +++ b/test/cypress/integration/ticket/ticketVolume.spec.js @@ -0,0 +1,27 @@ +/// +function checkRightLabel(index, value, tag = 'Volume: ') { + cy.get(`.q-scrollarea__content > :nth-child(${index}) > :nth-child(2) > span`) + .should('be.visible') + .should('have.text', `${tag}${value}`); +} +describe('TicketVolume', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/1/volume'); + }); + + it('Check right panel info', () => { + cy.get('.q-page').should('be.visible'); + checkRightLabel(2, '0.028'); + checkRightLabel(3, '0.014'); + checkRightLabel(4, '1.526'); + }); + it('Descriptors', () => { + cy.get(':nth-child(1) > [data-col-field="itemFk"]') + .find('span') + .should('have.class', 'link') + .click(); + cy.dataCy('ItemDescriptor').should('exist'); + }); +}); diff --git a/test/cypress/integration/vnComponent/crudModel.commands.js b/test/cypress/integration/vnComponent/crudModel.commands.js new file mode 100644 index 000000000..9d08f064a --- /dev/null +++ b/test/cypress/integration/vnComponent/crudModel.commands.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('saveCrudModel', () => + cy.dataCy('crudModelDefaultSaveBtn').should('exist').click(), +); diff --git a/test/cypress/integration/vnComponent/formModel.commands.js b/test/cypress/integration/vnComponent/formModel.commands.js new file mode 100644 index 000000000..2814b0091 --- /dev/null +++ b/test/cypress/integration/vnComponent/formModel.commands.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('saveFormModel', () => + cy.dataCy('FormModelPopup_save').should('exist').click(), +); diff --git a/test/cypress/integration/vnComponent/vnConfirm.commands.js b/test/cypress/integration/vnComponent/vnConfirm.commands.js new file mode 100644 index 000000000..9f93967d6 --- /dev/null +++ b/test/cypress/integration/vnComponent/vnConfirm.commands.js @@ -0,0 +1,3 @@ +Cypress.Commands.add('confirmVnConfirm', () => + cy.dataCy('VnConfirm_confirm').should('exist').click(), +); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 610fc78f2..ac84a5e60 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -78,20 +78,21 @@ Cypress.Commands.add('waitForElement', (element) => { Cypress.Commands.add('getValue', (selector) => { cy.get(selector).then(($el) => { if ($el.find('.q-checkbox__inner').length > 0) { - return cy.get(selector + '.q-checkbox__inner'); + return cy.get(`${selector}.q-checkbox__inner`); } // Si es un QSelect 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', + `${ + selector + }> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input`, ) .invoke('val'); } // Si es un QSelect if ($el.find('span').length) { - return cy.get(selector + ' span').then(($span) => { + return cy.get(`${selector} span`).then(($span) => { return $span[0].innerText; }); } @@ -147,7 +148,7 @@ function selectItem(selector, option, ariaControl, hasWrite = true) { 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 }) + .get(`#${ariaControl}`, { timeout }) .should('exist') .find('.q-item') .should('exist') @@ -471,7 +472,7 @@ Cypress.Commands.add('clickButtonWithIcon', (iconClass) => { cy.waitForElement('[data-cy="descriptor_actions"]'); cy.waitSpinner(); cy.get('.q-btn') - .filter((index, el) => Cypress.$(el).find('.q-icon.' + iconClass).length > 0) + .filter((index, el) => Cypress.$(el).find(`.q-icon.${iconClass}`).length > 0) .then(($btn) => { cy.wrap($btn).click(); }); @@ -626,21 +627,11 @@ Cypress.Commands.add('checkQueryParams', (expectedParams = {}) => { }); }); -Cypress.Commands.add('waitTableScrollLoad', () => - cy.waitForElement('[data-q-vs-anchor]'), -); -Cypress.Commands.add('countTableRows', (operator = 'gt', value = 0) => { - const element = 'data-row-index'; - cy.get('body').then(($body) => { - const hasRows = $body.find(`td[${element}]`).length > 0; - if (!hasRows) expect(0).to.be[operator](value); - else - cy.get(`td[${element}]`) - .last() - .invoke('attr', element) - .then((lastIndex) => { - const totalRows = parseInt(lastIndex) + 1; - expect(totalRows).to.be[operator](value); - }); +Cypress.Commands.add('validateScrollContent', (validations) => { + validations.forEach(({ row, col, text }) => { + cy.get(`.q-scrollarea__content > :nth-child(${row}) > :nth-child(${col})`).should( + 'have.text', + text, + ); }); }); diff --git a/vitest.config.js b/vitest.config.js index 331d21ef9..c2c3661a9 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -17,6 +17,7 @@ if (process.env.CI) { // https://vitejs.dev/config/ export default defineConfig({ test: { + globals: true, reporters, outputFile, environment: 'happy-dom',
{{ subtitle }}