From 98552bcd9e0f4f03480a66efe17a79dcf971b03b Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 21 Aug 2023 15:14:19 +0200 Subject: [PATCH] refs #5673 test(CrudModel): front and e2e --- .eslintrc.js | 2 +- cypress/fixtures/example.json | 5 + cypress/support/commands.js | 25 ++++ src/components/CrudModel.vue | 4 +- src/components/FormModel.vue | 7 +- src/pages/Claim/Card/ClaimCard.vue | 15 ++- src/stores/useStateStore.js | 11 +- .../integration/claimDevelopment.spec.js | 108 +++++++++++------- test/cypress/support/commands.js | 67 +++++++++++ .../components/common/CrudModel.spec.js | 40 +++++-- test/vitest/helper.js | 5 + 11 files changed, 218 insertions(+), 71 deletions(-) create mode 100644 cypress/fixtures/example.json create mode 100644 cypress/support/commands.js diff --git a/.eslintrc.js b/.eslintrc.js index 09dc09c1e3..c8bdecb1a4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -64,7 +64,7 @@ module.exports = { }, overrides: [ { - files: ['test/cypress/**/*.spec.{js,ts}'], + files: ['test/cypress/**/*.*'], extends: [ // Add Cypress-specific lint rules, globals and Cypress plugin // See https://github.com/cypress-io/eslint-plugin-cypress#rules diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000000..02e4254378 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000000..66ea16ef0e --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) \ No newline at end of file diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 6b8c577a06..280282ffda 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -285,8 +285,8 @@ function isEmpty(obj) { </template> </VnPaginate> <SkeletonTable v-if="!formData" /> - <Teleport to="#st-actions" v-if="stateStore.isSubToolbarShown()"> - <QBtnGroup push class="q-gutter-x-sm"> + <Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()"> + <QBtnGroup push flat class="q-gutter-x-sm"> <slot name="moreActions" /> <QBtn :label="tMobile('globals.remove')" diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index 3c05333f54..28a3759b89 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -65,7 +65,7 @@ async function fetch() { }); state.set($props.model, data); - originalData.value = Object.assign({}, data); + originalData.value = data && JSON.parse(JSON.stringify(data)); watch(formData.value, () => (hasChanges.value = true)); @@ -82,13 +82,14 @@ async function save() { isLoading.value = true; await axios.patch($props.urlUpdate || $props.url, formData.value); - originalData.value = formData.value; + originalData.value = JSON.parse(JSON.stringify(formData.value)); hasChanges.value = false; isLoading.value = false; } function reset() { state.set($props.model, originalData.value); + watch(formData.value, () => (hasChanges.value = true)); hasChanges.value = false; } // eslint-disable-next-line vue/no-dupe-keys @@ -120,7 +121,7 @@ watch(formUrl, async () => { <QForm v-if="formData" @submit="save" @reset="reset" class="q-pa-md"> <slot name="form" :data="formData" :validate="validate" :filter="filter"></slot> </QForm> - <Teleport to="#st-actions" v-if="stateStore.isSubToolbarShown()"> + <Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()"> <div v-if="$props.defaultActions"> <QBtnGoup push class="q-gutter-x-sm"> <slot name="moreActions" /> diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue index 142657ba53..d52b918e18 100644 --- a/src/pages/Claim/Card/ClaimCard.vue +++ b/src/pages/Claim/Card/ClaimCard.vue @@ -29,7 +29,6 @@ const claimSections = [ let salixUrl; onMounted(async () => { salixUrl = await getUrl(`claim/${entityId.value}`); - stateStore.setSubtoolbar(); }); </script> <template> @@ -65,13 +64,13 @@ onMounted(async () => { </QScrollArea> </QDrawer> <QPageContainer> - <QToolbar class="bg-vn-dark justify-end"> - <div id="st-data"></div> - <QSpace /> - <div id="st-actions"></div> - </QToolbar> - <QPage class="q-pa-md"> - <RouterView></RouterView> + <QPage> + <QToolbar class="bg-vn-dark justify-end"> + <div id="st-data"></div> + <QSpace /> + <div id="st-actions"></div> + </QToolbar> + <div class="q-pa-md"><RouterView></RouterView></div> </QPage> </QPageContainer> </template> diff --git a/src/stores/useStateStore.js b/src/stores/useStateStore.js index cf259a7670..74b65e71f0 100644 --- a/src/stores/useStateStore.js +++ b/src/stores/useStateStore.js @@ -5,7 +5,6 @@ export const useStateStore = defineStore('stateStore', () => { const isMounted = ref(false); const leftDrawer = ref(false); const rightDrawer = ref(false); - const subToolbar = ref(false); function toggleLeftDrawer() { leftDrawer.value = !leftDrawer.value; @@ -19,10 +18,6 @@ export const useStateStore = defineStore('stateStore', () => { isMounted.value = true; } - function setSubtoolbar() { - subToolbar.value = true; - } - function isHeaderMounted() { return isMounted.value; } @@ -36,7 +31,10 @@ export const useStateStore = defineStore('stateStore', () => { } function isSubToolbarShown() { - return subToolbar.value; + return ( + !!document.querySelector('#st-data') && + !!document.querySelector('#st-actions') + ); } return { @@ -48,7 +46,6 @@ export const useStateStore = defineStore('stateStore', () => { toggleRightDrawer, isLeftDrawerShown, isRightDrawerShown, - setSubtoolbar, isSubToolbarShown, }; }); diff --git a/test/cypress/integration/claimDevelopment.spec.js b/test/cypress/integration/claimDevelopment.spec.js index ec41e92a36..f05710eeaf 100755 --- a/test/cypress/integration/claimDevelopment.spec.js +++ b/test/cypress/integration/claimDevelopment.spec.js @@ -1,55 +1,77 @@ /// <reference types="cypress" /> -describe('ClaimPhoto', () => { +describe('ClaimDevelopment', () => { + const claimId = 1; + beforeEach(() => { - const claimId = 1; + cy.viewport(1920, 1080); cy.login('developer'); - cy.visit(`/#/claim/${claimId}/photos`); + cy.visit(`/#/claim/${claimId}/development`); }); - it('should add new file', () => { - cy.get('label > .q-btn').click(); - cy.get('label > .q-btn input').selectFile('test/cypress/fixtures/image.jpg', { - force: true, - }); - cy.get('.q-notification__message').should('have.text', 'Data saved'); - }); - - it('should add new file with drag and drop', () => { - cy.get('.container').selectFile('test/cypress/fixtures/image.jpg', { - action: 'drag-drop', - }); - cy.get('.q-notification__message').should('have.text', 'Data saved'); - }); - - it('should open first image dialog change to second and close', () => { + it('should reset line', () => { + cy.get('tbody > :nth-child(1) > :nth-child(2)').click(); + cy.selectOption('Novato'); + cy.get('[title="Reset"]').click(); cy.get( - ':nth-child(1) > .q-card > .q-img > .q-img__container > .q-img__image' - ).click(); - cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( - 'be.visible' - ); - - cy.get('.q-carousel__control > .q-btn > .q-btn__content > .q-icon').click(); - - cy.get( - '.q-dialog__inner > .q-toolbar > .q-btn > .q-btn__content > .q-icon' - ).click(); - cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( - 'not.be.visible' - ); + ':nth-child(1) > :nth-child(2) > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > span' + ).should('have.text', 'Prisas'); }); - it('should remove third and fourth file', () => { - cy.get( - '.multimediaParent > :nth-child(3) > .q-btn > .q-btn__content > .q-icon' - ).click(); - cy.get('.q-btn--unelevated > .q-btn__content > .block').click(); - cy.get('.q-notification__message').should('have.text', 'Data deleted'); + it('should edit line', () => { + cy.get('tbody > :nth-child(1) > :nth-child(2)').click(); + cy.selectOption('Novato'); + cy.get('[title="Save"]').click(); + cy.visit(`/#/claim/${claimId}/development`); cy.get( - '.multimediaParent > :nth-child(3) > .q-btn > .q-btn__content > .q-icon' - ).click(); - cy.get('.q-btn--unelevated > .q-btn__content > .block').click(); - cy.get('.q-notification__message').should('have.text', 'Data deleted'); + ':nth-child(1) > :nth-child(2) > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > span' + ).should('have.text', 'Novato'); + + //Restart data + cy.get('tbody > :nth-child(1) > :nth-child(2)'); + cy.selectOption('Prisas'); + cy.get('[title="Save"]').click(); }); + + it('should add new line', () => { + //check third if row exist + cy.get('.q-page-sticky > div > .q-btn').click(); + cy.get('tbody > :nth-child(3)').should('exist'); + + //fill in data + const rowData = ['', '', '']; + cy.fillTableRow(3, rowData); + }); + + // it('should remove last line', () => { + // cy.get( + // ':nth-child(1) > .q-card > .q-img > .q-img__container > .q-img__image' + // ).click(); + // cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( + // 'be.visible' + // ); + + // cy.get('.q-carousel__control > .q-btn > .q-btn__content > .q-icon').click(); + + // cy.get( + // '.q-dialog__inner > .q-toolbar > .q-btn > .q-btn__content > .q-icon' + // ).click(); + // cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( + // 'not.be.visible' + // ); + // }); + + // it('should remove third and fourth file', () => { + // cy.get( + // '.multimediaParent > :nth-child(3) > .q-btn > .q-btn__content > .q-icon' + // ).click(); + // cy.get('.q-btn--unelevated > .q-btn__content > .block').click(); + // cy.get('.q-notification__message').should('have.text', 'Data deleted'); + + // cy.get( + // '.multimediaParent > :nth-child(3) > .q-btn > .q-btn__content > .q-icon' + // ).click(); + // cy.get('.q-btn--unelevated > .q-btn__content > .block').click(); + // cy.get('.q-notification__message').should('have.text', 'Data deleted'); + // }); }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index a3a61c4232..eca5f6c8ca 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -40,4 +40,71 @@ Cypress.Commands.add('login', (user) => { window.localStorage.setItem('token', response.body.token); }); }); + +Cypress.Commands.add('selectOption', (option) => { + //cy.visit('/#/login'); + cy.get('.q-item__label').then(() => { + cy.contains('.q-item__label', option).click(); + }); +}); + +// Cypress.Commands.add('fillRow', (row, options) => { +// //cy.visit('/#/login'); +// for (let [i, option] of options.entries()) { +// i++; +// console.log(i); +// const selector = `tbody > :nth-child(${row}) > :nth-child(${i})`; +// if (cy.get(selector).should('have.class', 'q-select')) +// cy.selectOption(selector, option); +// } +// }); +Cypress.Commands.add('fillTableRow', (rowNumber, data) => { + // Obtener todas las filas de la tabla + cy.get('table tbody tr').eq(rowNumber).as('currentRow'); + + // Iterar sobre cada dato en el array 'data' + data.forEach((value, index) => { + // Basándonos en el índice, encontramos la celda correspondiente y verificamos el tipo de input + cy.get('@currentRow') + .find('td') + .eq(index) + .find('input') + .invoke('attr', 'type') + .then((type) => { + switch (type) { + case 'text': + cy.get('@currentRow') + .find('td') + .eq(index) + .find('input[type="text"]') + .clear() + .type(value); + break; + + case 'checkbox': + if (value) { + // Puede adaptar esto según cómo represente los valores booleanos en su array 'data' + cy.get('@currentRow') + .find('td') + .eq(index) + .find('input[type="checkbox"]') + .check(); + } else { + cy.get('@currentRow') + .find('td') + .eq(index) + .find('input[type="checkbox"]') + .uncheck(); + } + break; + + // ... Puede agregar más casos para otros tipos de inputs según sea necesario + + default: + // Manejar cualquier otro tipo de input o agregar lógica de error aquí si es necesario + break; + } + }); + }); +}); // registerCommands(); diff --git a/test/vitest/__tests__/components/common/CrudModel.spec.js b/test/vitest/__tests__/components/common/CrudModel.spec.js index 6547437ca4..518c3ad605 100644 --- a/test/vitest/__tests__/components/common/CrudModel.spec.js +++ b/test/vitest/__tests__/components/common/CrudModel.spec.js @@ -1,13 +1,16 @@ -import { createWrapper } from 'app/test/vitest/helper'; +import { createWrapper, axios } from 'app/test/vitest/helper'; import CrudModel from 'components/CrudModel.vue'; -import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { vi, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'; describe.only('CrudModel', () => { let vm; beforeAll(() => { vm = createWrapper(CrudModel, { global: { - stubs: ['VnPaginate', 'useState'], + stubs: ['vnPaginate', 'useState', 'arrayData', 'useStateStore'], + mocks: { + fetch: vi.fn(), + }, }, propsData: { dataRequired: { @@ -15,6 +18,15 @@ describe.only('CrudModel', () => { name: 'name', autoLoad: true, }, + dataKey: 'crudModelKey', + model: 'crudModel', + url: 'crudModelUrl', + }, + attrs: { + url: 'crudModelUrl', + dataKey: 'CustomerList', + order: 'id DESC', + limit: 3, }, }).vm; }); @@ -24,12 +36,26 @@ describe.only('CrudModel', () => { }); describe('insert()', () => { - it('should new element in list', () => { + it('should new element in list with index 0 if formData not has data', () => { + vi.mock('src/composables/useValidator', () => ({ + default: () => {}, + fetch: () => { + vi.fn(); + }, + })); + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [ + { id: 1, name: 'Tony Stark' }, + { id: 2, name: 'Jessica Jones' }, + { id: 3, name: 'Bruce Wayne' }, + ], + }); + vm.state.set('crudModel', []); vm.insert(); - expect(vm.message).toEqual( - `A minimum amount of 50€ (VAT excluded) is required for your order ${orderId} of ${shipped} to receive it without additional shipping costs.` - ); + expect(vm.formData.length).toEqual(1); + expect(vm.formData[0].id).toEqual(1); + expect(vm.formData[0].$index).toEqual(0); }); }); }); diff --git a/test/vitest/helper.js b/test/vitest/helper.js index 8a6fb14159..186f4ee3a4 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -64,6 +64,10 @@ export function createWrapper(component, options) { global: { plugins: [i18n, pinia], }, + mocks: { + t: (tKey) => tKey, + $t: (tKey) => tKey, + }, }; const mountOptions = Object.assign({}, defaultOptions); @@ -75,6 +79,7 @@ export function createWrapper(component, options) { mountOptions.global.plugins = defaultOptions.global.plugins; } } + console.log(mountOptions); const wrapper = mount(component, mountOptions); const vm = wrapper.vm;