From e49a91255f646036ca4d8f0031594ee194f5833f Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Sun, 20 Oct 2024 01:46:20 +0200 Subject: [PATCH] feat: refs #7220 ok FormModel --- cypress.config.js | 8 +- src/stores/useArrayDataStore.js | 5 +- src/stores/useArrayDataStore.test.js | 22 +++ .../components/CreateBankEntityForm.spec.js | 92 ++++++++++-- test/cypress/components/FormModel.spec.js | 142 +++++++++++++++++- test/cypress/support/component.js | 99 +++++++++++- 6 files changed, 339 insertions(+), 29 deletions(-) create mode 100644 src/stores/useArrayDataStore.test.js diff --git a/cypress.config.js b/cypress.config.js index b30df2b3d..f629c5b28 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -4,6 +4,9 @@ const { } = require('@quasar/quasar-app-extension-testing-e2e-cypress/cct-dev-server'); module.exports = defineConfig({ + env: { + baseUrl: 'http://localhost:9000/', + }, e2e: { baseUrl: 'http://localhost:9000/', experimentalStudio: true, @@ -33,7 +36,10 @@ module.exports = defineConfig({ indexHtmlFile: 'test/cypress/support/component-index.html', supportFile: 'test/cypress/support/component.js', specPattern: 'test/cypress/components/**/*.spec.js', - devServer: injectQuasarDevServerConfig(), + devServer: { + framework: 'quasar', + bundler: 'vite', + }, setupNodeEvents(on, config) { // implement node event listeners here on('after:spec', (results) => { diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index ea029bc12..7efda1430 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -1,8 +1,9 @@ -import { ref } from 'vue'; import { defineStore } from 'pinia'; +import { ref } from 'vue'; export const useArrayDataStore = defineStore('arrayDataStore', () => { const state = ref({}); + const defaultOpts = { filter: {}, userFilter: {}, @@ -24,7 +25,7 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { } function set(key) { - state.value[key] = getDefaultState(); + state.value[key] = getDefaultState(); // Ahora asigna el estado a la clave 'key' } function clear(key) { diff --git a/src/stores/useArrayDataStore.test.js b/src/stores/useArrayDataStore.test.js new file mode 100644 index 000000000..2543f030f --- /dev/null +++ b/src/stores/useArrayDataStore.test.js @@ -0,0 +1,22 @@ +import { setActivePinia, createPinia } from 'pinia'; +import { useArrayDataStore } from './useArrayDataStore'; + +describe('useArrayDataStore', () => { + beforeEach(() => { + setActivePinia(createPinia()); + }); + + it('should initialize isLoading to false', () => { + const store = useArrayDataStore(); + store.set('testKey'); + expect(store.get('testKey').isLoading).toBe(false); + }); + + it('should reset isLoading to false', () => { + const store = useArrayDataStore(); + store.set('testKey'); + store.get('testKey').isLoading = true; + store.reset('testKey', ['isLoading']); + expect(store.get('testKey').isLoading).toBe(false); + }); +}); diff --git a/test/cypress/components/CreateBankEntityForm.spec.js b/test/cypress/components/CreateBankEntityForm.spec.js index c4adc76dc..0739c0e1c 100644 --- a/test/cypress/components/CreateBankEntityForm.spec.js +++ b/test/cypress/components/CreateBankEntityForm.spec.js @@ -1,31 +1,34 @@ +import { createTestingPinia } from '@pinia/testing'; import axios from 'axios'; +import { setActivePinia } from 'pinia'; +import { createPinia } from 'pinia'; import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue'; import { useArrayData } from 'src/composables/useArrayData'; import { useArrayDataStore } from 'src/stores/useArrayDataStore'; -// import { useRouter } from 'vue-router'; +import { useRouter } from 'vue-router'; // import vueRouter from 'vue-router'; describe('', () => { const mockApiResponse = { results: [{ id: 1, name: 'Test' }] }; - const arrayDataStore = useArrayDataStore(); + // const arrayDataStore = useArrayDataStore(); const mockStoreData = { filter: {}, userFilter: {}, userParams: {}, url: 'mockUrl' }; - + let store, arrayData; before(() => { cy.login('developer'); // cy.stub(arrayDataStore, 'get').callsFake(() => 'asd'); - cy.stub(arrayDataStore, 'get') - .callsFake((e, fn) => (e = fn)) - .as('update'); - const arrayData = useArrayData('ArrayData', { url: 'mockUrl' }); + // cy.stub(arrayDataStore, 'get') + // .callsFake((e, fn) => (e = fn)) + // .as('update'); + // const arrayData = useArrayData('ArrayData', { url: 'mockUrl' }); - cy.stub(arrayData).callsFake(() => ({ - userParams: {}, - userFilter: {}, - order: {}, - searchUrl: 'searchUrl', - })); - // cy.stub(vueRouter, 'useRouter').callsFake(() => ({ + // cy.stub(arrayData).callsFake(() => ({ + // userParams: {}, + // userFilter: {}, + // order: {}, + // searchUrl: 'searchUrl', + // })); + // cy.stub(vueRouter).callsFake(() => ({ // push: () => {}, // replace: () => {}, // currentRoute: { @@ -38,14 +41,69 @@ describe('', () => { // }, // }, // })); + Cypress.config('defaultCommandTimeout', 500); + + // const spy = cy.spy(); + // createTestingPinia({ + // createSpy: () => spy, + // }); + + // // one way: + // store = useArrayDataStore(); + + // // another way with the same error: + // useArrayDataStore( + // createTestingPinia({ + // createSpy: () => spy, + // initialState: { + // state: { ArrayData: cy.spy() }, + // }, + // }) + // ); + + // cy.stub(useArrayDataStore, 'get').callsFake(() => cy.spy()); + // cy.stub(useArrayData, 'get').callsFake(() => store); + // arrayData = useArrayData('ArrayData', { url: 'mockUrl' }); }); beforeEach(() => { + // cy._mount(CreateBankEntityForm, { + // global: { + // plugins: [store], + // }, + // }); // Stub del método get del store // cy.spy(useRouter(), 'replace'); // cy.spy(useRouter(), 'push'); }); it('should intercept axios.get for a specific route', () => { + // Configura Pinia y la store + const pinia = createPinia(); + setActivePinia(pinia); + const arrayDataStore = useArrayDataStore(); + cy.stub(arrayDataStore, 'get').returns(mockStoreData).as('getStub'); + + cy.stub(arrayData, 'arrayDataStore').callsFake(() => store); + cy.createWrapper(CreateBankEntityForm); + // Obtén la instancia de la store de Pinia + // const store = useArrayDataStore(); + // Asegúrate de que el método `set` esté disponible + // Usar cy.stub() para espiar la función set y personalizar su comportamiento + // cy.stub(store, 'set').callsFake(() => { + // console.log('store.set fue llamado'); + // // Simular que set se ejecuta correctamente + // store.get('key').data = 'Fake Data'; + // }); + // expect(store.set).to.be.a('function'); + // store.set('key'); // Inicializar el estado con `set` + // store.get('key').data = 'Test Data'; // Simular que tenemos datos + // // Simula que agregamos usuarios a la store + // store.setUsers([ + // { id: 1, name: 'John Doe' }, + // { id: 2, name: 'Jane Doe' }, + // ]); + }); + xit('should intercept axios.get for a specific route', () => { // Intercepta la llamada a axios.get para una ruta específica cy.stub(axios, 'get') .callsFake((url, params) => { @@ -58,7 +116,11 @@ describe('', () => { .as('axiosStub'); const onFetchSpy = cy.spy().as('onFetchSpy'); - + // Configura Pinia y la store + const pinia = createPinia(); + setActivePinia(pinia); + const arrayDataStore = useArrayDataStore(); + cy.stub(arrayDataStore, 'get').returns(mockStoreData).as('getStub'); cy.createWrapper(CreateBankEntityForm, { props: { showEntityField: true, diff --git a/test/cypress/components/FormModel.spec.js b/test/cypress/components/FormModel.spec.js index 859c1f9a1..22cf8ab82 100644 --- a/test/cypress/components/FormModel.spec.js +++ b/test/cypress/components/FormModel.spec.js @@ -1,8 +1,142 @@ +import { Notify } from 'quasar'; import FormModel from 'src/components/FormModel.vue'; +// import useNotify from 'src/composables/useNotify'; +import { useState } from 'src/composables/useState'; +import { useStateStore } from 'src/stores/useStateStore'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(FormModel, { props: {} }); +describe('', () => { + let piniaOptions = null; + let saveSpy, notifySpy, component, wrapper; + beforeEach(() => { + piniaOptions = { + stubActions: false, + createSpy: cy.spy, // Use Cypress spy function + }; + const state = useState(); + const stateStore = useStateStore(); + cy.stub(stateStore, 'isSubToolbarShown').returns(true).as('isSubToolbarShown'); + state.set('testModel', { name: 'Test', age: 30 }); + const props = { + url: '', + model: 'testModel', + filter: {}, + urlUpdate: '', + urlCreate: '/api/test/create', + defaultActions: true, + defaultButtons: {}, + autoLoad: false, + formInitialData: {}, + observeFormChanges: true, + mapper: null, + clearStoreOnUnmount: true, + saveFn: null, + goTo: '', + reload: false, + defaultTrim: true, + }; + cy.mount(FormModel, { + piniaOptions, + props, + slots: { + form: ``, + moreActions: ``, + }, + }).then(({ component: cmp, wrapper: wpr }) => { + component = cmp; + wrapper = wpr; + saveSpy = cy.spy(component, 'save').as('saveSpy'); + notifySpy = cy.spy(Notify, 'create').as('notifySpy'); + }); }); + it('should mount the component', () => { + cy.get('#formModel').should('exist'); + }); + + it('should call the save method when the save button is clicked without changes', () => { + cy.get('#formModel').should('exist'); + cy.get('button').contains('Save').click(); + cy.get('@saveSpy').should('not.have.been.called'); + cy.get('@notifySpy').should((spy) => { + expect(spy).to.have.been.calledWith({ + message: 'No changes to save', + type: 'negative', + icon: 'error', + }); + }); + }); + it('should call the save method when the save button is clicked with changes', () => { + cy.get('#formModel').should('exist'); + cy.get('[data-cy="name"]').type('John Doe'); + cy.get('button').contains('Save').click(); + cy.get('@saveSpy').should('not.have.been.called'); + cy.get('@notifySpy').should((spy) => { + const [args] = spy.getCall(0)?.args || []; + expect(args.type).to.equal('negative'); + expect(args.icon).to.equal('error'); + expect(args.message).not.to.equal('No changes to save'); + }); + }); + /* + it('should display the confirmation dialog on route leave with unsaved changes', () => { + // Simulate form change + cy.get('#formModel').then((form) => { + form[0].__vue__.formData.name = 'Changed'; + }); + + // Attempt to navigate away + cy.get('.q-dialog').should('be.visible'); + cy.get('.q-dialog .q-dialog__title').should( + 'contain', + 'globals.unsavedPopup.title' + ); + }); + + it('should call save method on form submit', async () => { + const saveSpy = cy.spy(formModel.value, 'save'); + await formModel.value.$refs.myForm.trigger('submit'); + expect(saveSpy).to.have.been.called; + }); + + it('should reset the form data', async () => { + formModel.value.formData.name = 'Changed'; + await formModel.value.reset(); + expect(formModel.value.formData.name).toBe('Test'); + }); + + it('should show loading indicator when saving', async () => { + formModel.value.isLoading = true; + await nextTick(); + expect(wrapper.findComponent({ name: 'QInnerLoading' }).props('showing')).toBe(true); + }); + + it('should render form slot content', () => { + expect(wrapper.find('input').exists()).toBe(true); + }); + + it('should update form data through slot inputs', async () => { + const nameInput = wrapper.find('input[type="text"]'); + const ageInput = wrapper.find('input[type="number"]'); + + await nameInput.setValue('Updated Name'); + await ageInput.setValue(25); + + expect(formModel.value.formData.name).toBe('Updated Name'); + expect(formModel.value.formData.age).toBe(25); + }); + + it('should render moreActions slot content', () => { + expect(wrapper.find('button').exists()).toBe(true); + }); + + it('should call custom action on button click', async () => { + const customActionSpy = cy.spy(formModel.value, 'customAction'); + await wrapper.find('button').trigger('click'); + expect(customActionSpy).to.have.been.called; + });*/ }); diff --git a/test/cypress/support/component.js b/test/cypress/support/component.js index 65e1bc61d..6def6fb64 100644 --- a/test/cypress/support/component.js +++ b/test/cypress/support/component.js @@ -1,9 +1,13 @@ +import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-e2e-cypress'; import { createTestingPinia } from '@pinia/testing'; import { mount } from 'cypress/vue'; import { i18n } from 'src/boot/i18n'; import { Quasar } from 'quasar'; +import { createPinia } from 'pinia'; const pinia = createTestingPinia({ createSpy: () => {}, stubActions: false }); - +import axios from 'axios'; +installQuasarPlugin({ plugins: { Dialog, Notify } }); +// setActivePinia(pinia); // // Run this code before each *test*. // beforeEach(() => { // // New Pinia @@ -13,10 +17,72 @@ const pinia = createTestingPinia({ createSpy: () => {}, stubActions: false }); // setActivePinia(pinia); // }); -function createWrapper(component, options) { +// Crear un comando personalizado para montar componentes con Pinia +// Cypress.Commands.add('mountWithPinia', (component, options = {}) => { +// const pinia = createPinia(); +// options.global = options.global || {}; +// options.global.plugins = [Quasar, createPinia()]; // Incluye Quasar y Pinia +// return mount(component, options); +// }); + +// Registrar Quasar y sus componentes + +setActivePinia(createPinia()); // Ensure Pinia is active +axios.defaults.baseURL = Cypress.env('baseUrl'); +Cypress.Commands.add('mount', (component, options = {}) => { + const pinia = createTestingPinia(options.piniaOptions); + const components = options.global?.components || {}; + const router = createRouter({ + history: createWebHistory(), + routes: [], + }); const defaultOptions = { global: { - plugins: [Quasar, i18n, pinia], + mocks: { + t: (tKey) => tKey, + $t: (tKey) => tKey, + + $axios: axios, // Añadir axios como un mock global + }, + plugins: [ + Quasar, + (app) => { + app.use(router); + app.use(i18n); + app.use(pinia); + }, + ], // Añade Quasar y Pinia como plugins globales + }, + }; + const mountOptions = Object.assign({}, defaultOptions, options); + + if (options.global) { + mountOptions.global.plugins = defaultOptions.global.plugins; + } + + return mount(component, mountOptions); + + // options.global.plugins.push({ + // install(app) { + // app.use(i18n); + // app.use(router); + // // app.use(arrayDataStore); + // }, + // }); + // const wrapper = mount(component, mountOptions); + // const { vm } = wrapper; + // return wrapper; +}); +function createWrapper(component, options = {}) { + const defaultOptions = { + global: { + plugins: [ + Quasar, + i18n, + createTestingPinia({ + createSpy: () => cy.spy(), + }), + ], stubs: [ 'router-link', 'router-view', @@ -36,9 +102,13 @@ function createWrapper(component, options) { $t: (tKey) => tKey, }, }; - + const arrayDataStore = useArrayDataStore(); const mountOptions = Object.assign({}, defaultOptions); - + options.global = options?.global || {}; + options.global.plugins = options.global?.plugins || []; + // options.global.plugins.push([Quasar, {}]); + // options.global.plugins.push([i18n]); + // options.global.plugins.push(createTestingPinia()); if (options instanceof Object) { Object.assign(mountOptions, options); @@ -57,8 +127,10 @@ function createWrapper(component, options) { install(app) { app.use(i18n); app.use(options.router); + // app.use(arrayDataStore); }, }); + const wrapper = mount(component, mountOptions); const vm = wrapper.vm; @@ -77,8 +149,7 @@ Cypress.Commands.add('createWrapper', createWrapper); Cypress.Commands.add('_vnMount', (component, options = {}) => { const globalConfig = { global: { - stubs: ['router-view', 'vue-i18n'], - plugins: [Quasar, i18n, pinia], + plugins: [Quasar, i18n], mocks: { t: (key) => key }, }, }; @@ -115,6 +186,11 @@ Cypress.Commands.add('_vnMount', (component, options = {}) => { import 'quasar/dist/quasar.css'; import { createRouter } from 'vue-router'; import { createMemoryHistory } from 'vue-router'; +import { setActivePinia } from 'pinia'; +import { useArrayDataStore } from 'src/stores/useArrayDataStore'; +import { createWebHistory } from 'vue-router'; +import { Dialog } from 'quasar'; +import { Notify } from 'quasar'; Cypress.Commands.add('vue', () => { return cy.wrap(Cypress.vueWrapper); }); @@ -152,3 +228,12 @@ Cypress.Commands.add('login', (user) => { }); }); }); + +// Define el comando personalizado +Cypress.Commands.add('stubUseArrayDataStore', (stubData = {}) => { + const arrayDataStore = useArrayDataStore(); + if (arrayDataStore.get.restore) { + arrayDataStore.get.restore(); + } + cy.stub(arrayDataStore, 'get').returns(stubData).as('getStub'); +});