From 7a53282122322c2fb94824375922062a2b447622 Mon Sep 17 00:00:00 2001 From: jorgep Date: Fri, 20 Dec 2024 12:00:32 +0100 Subject: [PATCH 1/7] feat: refs #7056 update route meta information and add FormModel tests --- .../__tests__/components/FormModel.spec.js | 145 ++++++++++++++++++ test/vitest/helper.js | 4 +- 2 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 test/vitest/__tests__/components/FormModel.spec.js diff --git a/test/vitest/__tests__/components/FormModel.spec.js b/test/vitest/__tests__/components/FormModel.spec.js new file mode 100644 index 000000000..a608d26d6 --- /dev/null +++ b/test/vitest/__tests__/components/FormModel.spec.js @@ -0,0 +1,145 @@ +import { describe, expect, it, beforeAll, vi, afterAll } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import FormModel from 'src/components/FormModel.vue'; + +describe('FormModel', () => { + const model = 'mockModel'; + const url = 'mockUrl'; + const formInitialData = { mockKey: 'mockVal' }; + + describe('modelValue', () => { + it('should use the provided model', () => { + const { vm } = mount({ propsData: { model: 'mockModel' } }); + expect(vm.modelValue).toBe('mockModel'); + }); + + it('should use the route meta title when model is not provided', () => { + const { vm } = mount({}); + expect(vm.modelValue).toBe('formModel_mockTitle'); + }); + }); + + describe('onMounted()', () => { + let mockGet; + + beforeAll(() => { + mockGet = vi.spyOn(axios, 'get').mockResolvedValue({ data: {} }); + }); + + afterAll(() => { + mockGet.mockRestore(); + }); + + it('should not fetch when has formInitialData', () => { + mount({ propsData: { url, model, autoLoad: true, formInitialData } }); + expect(mockGet).not.toHaveBeenCalled(); + }); + + it('should fetch when there is url and auto-load', () => { + mount({ propsData: { url, model, autoLoad: true } }); + expect(mockGet).toHaveBeenCalled(); + }); + + it('should not observe changes', () => { + const { vm } = mount({ + propsData: { url, model, observeFormChanges: false, formInitialData }, + }); + + expect(vm.hasChanges).toBe(true); + vm.reset(); + expect(vm.hasChanges).toBe(true); + }); + + it('should observe changes', async () => { + const { vm } = mount({ + propsData: { url, model, formInitialData }, + }); + vm.state.set(model, { mockKey: 'mockVal' }); + expect(vm.hasChanges).toBe(false); + + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + expect(vm.hasChanges).toBe(true); + }); + }); + + describe.skip('watch()', () => { + let wrapper; + let vm; + + beforeAll(() => { + wrapper = mount({ + propsData: { url, model, formInitialData }, + }); + vm = wrapper.vm; + }); + + it('should call updateAndEmit when arrayData.store.data changes', async () => { + const updateAndEmitSpy = vi.spyOn(vm, 'updateAndEmit'); + await vm.$nextTick(); + console.log('vm.arrayData.store.data', vm.arrayData.store.data); + vm.arrayData.store.data = { newData: 'newValue' }; + await vm.$nextTick(); + vm.arrayData.store.data = { newData: 'anotherVal' }; + await vm.$nextTick(); + + expect(updateAndEmitSpy).toHaveBeenCalled(); + }); + + it('should call reset and fetch when $props.url or $props.filter changes', async () => { + const resetSpy = vi.spyOn(vm, 'reset'); + const fetchSpy = vi.spyOn(vm, 'fetch'); + + wrapper.setProps({ url: 'newMockUrl' }); + await wrapper.vm.$nextTick(); + expect(resetSpy).toHaveBeenCalled(); + expect(fetchSpy).toHaveBeenCalled(); + }); + }); + + describe('trimData()', () => { + let vm; + beforeAll(() => { + vm = mount({}).vm; + }); + + it('should trim whitespace from string values', () => { + const data = { key1: ' value1 ', key2: ' value2 ' }; + const trimmedData = vm.trimData(data); + expect(trimmedData).toEqual({ key1: 'value1', key2: 'value2' }); + }); + + it('should not modify non-string values', () => { + const data = { key1: 123, key2: true, key3: null, key4: undefined }; + const trimmedData = vm.trimData(data); + expect(trimmedData).toEqual(data); + }); + }); + + describe('onUnmounted()', () => { + it('should restore original data in the store if changes were made but not saved', async () => { + const wrapper = mount({ propsData: { model, formInitialData } }); + const vm = wrapper.vm; + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await wrapper.unmount(); + expect(vm.state.get(model)).toEqual(formInitialData); + }); + + it('should clear the store on unmount if clearStoreOnUnmount is true', async () => { + const wrapper = mount({ + propsData: { model, formInitialData, clearStoreOnUnmount: true }, + }); + const vm = wrapper.vm; + vm.hasChanges = false; + await wrapper.unmount(); + expect(vm.state.get(model)).toBeUndefined(); + }); + }); +}); + +function mount({ propsData = {} }) { + return createWrapper(FormModel, { + propsData, + }); +} diff --git a/test/vitest/helper.js b/test/vitest/helper.js index ce057c7c3..1e693ab63 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -26,7 +26,7 @@ vi.mock('vue-router', () => ({ params: { id: 1, }, - meta: { moduleName: 'mockName' }, + meta: { moduleName: 'mockModuleName' }, matched: [{ path: 'mockName/list' }], }, }, @@ -35,7 +35,7 @@ vi.mock('vue-router', () => ({ matched: [], query: {}, params: {}, - meta: { moduleName: 'mockName' }, + meta: { moduleName: 'mockModuleName', title: 'mockTitle', name: 'mockName' }, path: 'mockSection/list', }), onBeforeRouteLeave: () => {}, From 48742289fef97cf9d4a2e9d4b11e114c848d8ca7 Mon Sep 17 00:00:00 2001 From: jorgep Date: Mon, 23 Dec 2024 09:45:15 +0100 Subject: [PATCH 2/7] feat: refs #7056 add tests in FormModel --- .../__tests__/components/FormModel.spec.js | 77 +++++++------------ 1 file changed, 27 insertions(+), 50 deletions(-) diff --git a/test/vitest/__tests__/components/FormModel.spec.js b/test/vitest/__tests__/components/FormModel.spec.js index a608d26d6..469e52770 100644 --- a/test/vitest/__tests__/components/FormModel.spec.js +++ b/test/vitest/__tests__/components/FormModel.spec.js @@ -54,46 +54,13 @@ describe('FormModel', () => { const { vm } = mount({ propsData: { url, model, formInitialData }, }); - vm.state.set(model, { mockKey: 'mockVal' }); + vm.state.set(model, formInitialData); expect(vm.hasChanges).toBe(false); vm.formData.mockKey = 'newVal'; await vm.$nextTick(); expect(vm.hasChanges).toBe(true); - }); - }); - - describe.skip('watch()', () => { - let wrapper; - let vm; - - beforeAll(() => { - wrapper = mount({ - propsData: { url, model, formInitialData }, - }); - vm = wrapper.vm; - }); - - it('should call updateAndEmit when arrayData.store.data changes', async () => { - const updateAndEmitSpy = vi.spyOn(vm, 'updateAndEmit'); - await vm.$nextTick(); - console.log('vm.arrayData.store.data', vm.arrayData.store.data); - vm.arrayData.store.data = { newData: 'newValue' }; - await vm.$nextTick(); - vm.arrayData.store.data = { newData: 'anotherVal' }; - await vm.$nextTick(); - - expect(updateAndEmitSpy).toHaveBeenCalled(); - }); - - it('should call reset and fetch when $props.url or $props.filter changes', async () => { - const resetSpy = vi.spyOn(vm, 'reset'); - const fetchSpy = vi.spyOn(vm, 'fetch'); - - wrapper.setProps({ url: 'newMockUrl' }); - await wrapper.vm.$nextTick(); - expect(resetSpy).toHaveBeenCalled(); - expect(fetchSpy).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; }); }); @@ -116,24 +83,34 @@ describe('FormModel', () => { }); }); - describe('onUnmounted()', () => { - it('should restore original data in the store if changes were made but not saved', async () => { - const wrapper = mount({ propsData: { model, formInitialData } }); - const vm = wrapper.vm; - vm.formData.mockKey = 'newVal'; - await vm.$nextTick(); - await wrapper.unmount(); - expect(vm.state.get(model)).toEqual(formInitialData); + describe('save()', async () => { + it('should not call if there are not changes', async () => { + const { vm } = mount({ propsData: { url, model } }); + + await vm.save(); + expect(vm.hasChanges).toBe(false); }); - it('should clear the store on unmount if clearStoreOnUnmount is true', async () => { - const wrapper = mount({ - propsData: { model, formInitialData, clearStoreOnUnmount: true }, + it('should call axios.patch with the right data', async () => { + const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); + const { vm } = mount({ propsData: { url, model, formInitialData } }); + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + expect(spy).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; + }); + + it('should call axios.post with the right data', async () => { + const spy = vi.spyOn(axios, 'post').mockResolvedValue({ data: {} }); + const { vm } = mount({ + propsData: { url, model, formInitialData, urlCreate: 'mockUrlCreate' }, }); - const vm = wrapper.vm; - vm.hasChanges = false; - await wrapper.unmount(); - expect(vm.state.get(model)).toBeUndefined(); + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + expect(spy).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; }); }); }); From bf41f338b70158f40045a0caf976589d8e5424e8 Mon Sep 17 00:00:00 2001 From: provira Date: Mon, 23 Dec 2024 12:13:58 +0100 Subject: [PATCH 3/7] feat: refs #7079 created VnLocation front test --- src/components/common/VnLocation.vue | 2 +- .../common/__tests__/VnLocation.spec.js | 100 ++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/components/common/__tests__/VnLocation.spec.js diff --git a/src/components/common/VnLocation.vue b/src/components/common/VnLocation.vue index a8840f243..f58222187 100644 --- a/src/components/common/VnLocation.vue +++ b/src/components/common/VnLocation.vue @@ -26,7 +26,7 @@ const locationProperties = [ (obj) => obj.country?.name, ]; -const formatLocation = (obj, properties) => { +const formatLocation = (obj, properties = locationProperties) => { const parts = properties.map((prop) => { if (typeof prop === 'string') { return obj[prop]; diff --git a/src/components/common/__tests__/VnLocation.spec.js b/src/components/common/__tests__/VnLocation.spec.js new file mode 100644 index 000000000..920afced8 --- /dev/null +++ b/src/components/common/__tests__/VnLocation.spec.js @@ -0,0 +1,100 @@ +import { createWrapper } from 'app/test/vitest/helper'; +import VnLocation from 'components/common/VnLocation.vue'; +import { vi, afterEach, expect, it, beforeEach, describe } from 'vitest'; + +function buildComponent(data) { + return createWrapper(VnLocation, { + global: { + stubs: [''], + props: { + location: data + } + }, + }).vm; +} + +afterEach(() => { + vi.clearAllMocks(); +}); + +describe('formatLocation', () => { + let locationBase; + + beforeEach(() => { + locationBase = { + postcode: '46680', + city: 'Algemesi', + province: { name: 'Valencia' }, + country: { name: 'Spain' } + }; + }); + + it('should return the postcode, city, province and country', () => { + const location = { ...locationBase }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('46680, Algemesi(Valencia), Spain'); + }); + + it('should return the postcode and country', () => { + const location = { ...locationBase, city: undefined }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('46680, Spain'); + }); + + it('should return the city, province and country', () => { + const location = { ...locationBase, postcode: undefined }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('Algemesi(Valencia), Spain'); + }); + + it('should return the country', () => { + const location = { ...locationBase, postcode: undefined, city: undefined, province: undefined }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('Spain'); + }); +}); + +describe('showLabel', () => { + let locationBase; + + beforeEach(() => { + locationBase = { + code: '46680', + town: 'Algemesi', + province: 'Valencia', + country: 'Spain' + }; + }); + + it('should show the label with postcode, city, province and country', () => { + const location = { ...locationBase }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('46680, Algemesi(Valencia), Spain'); + }); + + it('should show the label with postcode and country', () => { + const location = { ...locationBase, town: undefined }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('46680, Spain'); + }); + + it('should show the label with city, province and country', () => { + const location = { ...locationBase, code: undefined }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('Algemesi(Valencia), Spain'); + }); + + it('should show the label with country', () => { + const location = { ...locationBase, code: undefined, town: undefined, province: undefined }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('Spain'); + }); +}); \ No newline at end of file From 093034c523e561622549decdacc8e8678b6a22dc Mon Sep 17 00:00:00 2001 From: jorgep Date: Mon, 23 Dec 2024 13:01:59 +0100 Subject: [PATCH 4/7] chore: refs #7056 move test --- .../components => src/components/__tests__}/FormModel.spec.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {test/vitest/__tests__/components => src/components/__tests__}/FormModel.spec.js (100%) diff --git a/test/vitest/__tests__/components/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js similarity index 100% rename from test/vitest/__tests__/components/FormModel.spec.js rename to src/components/__tests__/FormModel.spec.js From 7cb4dfe16a51e56f86a4f19ceb20e0f0ec6d43f3 Mon Sep 17 00:00:00 2001 From: jorgep Date: Mon, 23 Dec 2024 13:34:34 +0100 Subject: [PATCH 5/7] test: refs #7056 add save function and reload data tests for FormModel component --- src/components/__tests__/FormModel.spec.js | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/components/__tests__/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js index 469e52770..66157d567 100644 --- a/src/components/__tests__/FormModel.spec.js +++ b/src/components/__tests__/FormModel.spec.js @@ -112,6 +112,33 @@ describe('FormModel', () => { expect(spy).toHaveBeenCalled(); vm.formData.mockKey = 'mockVal'; }); + + it('should use the saveFn', async () => { + const { vm } = mount({ + propsData: { url, model, formInitialData, saveFn: () => {} }, + }); + const spyPatch = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); + const spySaveFn = vi.spyOn(vm.$props, 'saveFn'); + + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + expect(spyPatch).not.toHaveBeenCalled(); + expect(spySaveFn).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; + }); + + it('should reload the data after save', async () => { + const { vm } = mount({ + propsData: { url, model, formInitialData, reload: true }, + }); + vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); + + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + vm.formData.mockKey = 'mockVal'; + }); }); }); From f4aee047ff7f77e2dfbbc3f5dc10a049a2e8cd58 Mon Sep 17 00:00:00 2001 From: jorgep Date: Mon, 23 Dec 2024 14:21:36 +0100 Subject: [PATCH 6/7] test: refs #7056 update FormModel.spec.js to use dynamic model value in tests --- src/components/__tests__/FormModel.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/__tests__/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js index 66157d567..e35684bc3 100644 --- a/src/components/__tests__/FormModel.spec.js +++ b/src/components/__tests__/FormModel.spec.js @@ -9,8 +9,8 @@ describe('FormModel', () => { describe('modelValue', () => { it('should use the provided model', () => { - const { vm } = mount({ propsData: { model: 'mockModel' } }); - expect(vm.modelValue).toBe('mockModel'); + const { vm } = mount({ propsData: { model } }); + expect(vm.modelValue).toBe(model); }); it('should use the route meta title when model is not provided', () => { From 89acb338a9ebbaf521e93ca39828c65af53cba1a Mon Sep 17 00:00:00 2001 From: provira Date: Tue, 24 Dec 2024 12:18:36 +0100 Subject: [PATCH 7/7] refactor: refs #7079 removed useless code --- .../common/__tests__/VnLocation.spec.js | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/components/common/__tests__/VnLocation.spec.js b/src/components/common/__tests__/VnLocation.spec.js index 920afced8..65fdae960 100644 --- a/src/components/common/__tests__/VnLocation.spec.js +++ b/src/components/common/__tests__/VnLocation.spec.js @@ -5,7 +5,6 @@ import { vi, afterEach, expect, it, beforeEach, describe } from 'vitest'; function buildComponent(data) { return createWrapper(VnLocation, { global: { - stubs: [''], props: { location: data } @@ -32,29 +31,25 @@ describe('formatLocation', () => { it('should return the postcode, city, province and country', () => { const location = { ...locationBase }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('46680, Algemesi(Valencia), Spain'); + expect(vm.formatLocation(location)).toEqual('46680, Algemesi(Valencia), Spain'); }); it('should return the postcode and country', () => { const location = { ...locationBase, city: undefined }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('46680, Spain'); + expect(vm.formatLocation(location)).toEqual('46680, Spain'); }); it('should return the city, province and country', () => { const location = { ...locationBase, postcode: undefined }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('Algemesi(Valencia), Spain'); + expect(vm.formatLocation(location)).toEqual('Algemesi(Valencia), Spain'); }); it('should return the country', () => { const location = { ...locationBase, postcode: undefined, city: undefined, province: undefined }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('Spain'); + expect(vm.formatLocation(location)).toEqual('Spain'); }); }); @@ -73,28 +68,24 @@ describe('showLabel', () => { it('should show the label with postcode, city, province and country', () => { const location = { ...locationBase }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('46680, Algemesi(Valencia), Spain'); + expect(vm.showLabel(location)).toEqual('46680, Algemesi(Valencia), Spain'); }); it('should show the label with postcode and country', () => { const location = { ...locationBase, town: undefined }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('46680, Spain'); + expect(vm.showLabel(location)).toEqual('46680, Spain'); }); it('should show the label with city, province and country', () => { const location = { ...locationBase, code: undefined }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('Algemesi(Valencia), Spain'); + expect(vm.showLabel(location)).toEqual('Algemesi(Valencia), Spain'); }); it('should show the label with country', () => { const location = { ...locationBase, code: undefined, town: undefined, province: undefined }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('Spain'); + expect(vm.showLabel(location)).toEqual('Spain'); }); }); \ No newline at end of file