From 3fb9b0de47521752bf39c8f39acf1e44184fe349 Mon Sep 17 00:00:00 2001 From: provira Date: Thu, 19 Dec 2024 12:32:15 +0100 Subject: [PATCH] refactor: refs #8320 moved front tests to their respective sections --- src/boot/specs/axios.spec.js | 65 ++++++ src/components/VnTable/specs/VnTable.spec.js | 47 ++++ .../common/specs/VnChangePassword.spec.js | 70 ++++++ .../common/specs/VnDiscount.spec.js | 28 +++ src/components/common/specs/VnLog.spec.js | 134 +++++++++++ .../common/specs/VnSmsDialog.spec.js | 58 +++++ src/components/specs/CrudModel.spec.js | 120 ++++++++++ src/components/specs/Leftmenu.spec.js | 94 ++++++++ src/components/ui/specs/Paginate.spec.js | 118 ++++++++++ src/components/ui/specs/VnLinkPhone.spec.js | 50 +++++ src/components/ui/specs/VnSms.spec.js | 25 +++ src/composables/specs/downloadFile.spec.js | 36 +++ src/composables/specs/getExchange.spec.js | 45 ++++ src/composables/specs/getTotal.spec.js | 55 +++++ .../specs/useAccountShortToStandard.spec.js | 9 + src/composables/specs/useAcl.spec.js | 110 +++++++++ src/composables/specs/useArrayData.spec.js | 98 ++++++++ src/composables/specs/useRole.spec.js | 73 ++++++ src/composables/specs/useSession.spec.js | 211 ++++++++++++++++++ src/composables/specs/useTokenConfig.spec.js | 31 +++ .../Card/specs/ClaimDescriptorMenu.spec.js | 33 +++ src/pages/Claim/Card/specs/ClaimLines.spec.js | 75 +++++++ .../Claim/Card/specs/ClaimLinesImport.spec.js | 47 ++++ src/pages/Claim/Card/specs/ClaimPhoto.spec.js | 114 ++++++++++ .../Payments/specs/CustomerPayments.spec.js | 38 ++++ src/pages/Login/specs/Login.spec.js | 49 ++++ .../Ticket/Card/specs/TicketBoxing.spec.js | 50 +++++ src/pages/Ticket/specs/TicketAdvance.spec.js | 120 ++++++++++ src/pages/Wagon/specs/WagonCreate.spec.js | 97 ++++++++ .../specs/WorkerNotificationsManager.spec.js | 33 +++ src/stores/specs/useStateQueryStore.spec.js | 58 +++++ 31 files changed, 2191 insertions(+) create mode 100644 src/boot/specs/axios.spec.js create mode 100644 src/components/VnTable/specs/VnTable.spec.js create mode 100644 src/components/common/specs/VnChangePassword.spec.js create mode 100644 src/components/common/specs/VnDiscount.spec.js create mode 100644 src/components/common/specs/VnLog.spec.js create mode 100644 src/components/common/specs/VnSmsDialog.spec.js create mode 100644 src/components/specs/CrudModel.spec.js create mode 100644 src/components/specs/Leftmenu.spec.js create mode 100644 src/components/ui/specs/Paginate.spec.js create mode 100644 src/components/ui/specs/VnLinkPhone.spec.js create mode 100644 src/components/ui/specs/VnSms.spec.js create mode 100644 src/composables/specs/downloadFile.spec.js create mode 100644 src/composables/specs/getExchange.spec.js create mode 100644 src/composables/specs/getTotal.spec.js create mode 100644 src/composables/specs/useAccountShortToStandard.spec.js create mode 100644 src/composables/specs/useAcl.spec.js create mode 100644 src/composables/specs/useArrayData.spec.js create mode 100644 src/composables/specs/useRole.spec.js create mode 100644 src/composables/specs/useSession.spec.js create mode 100644 src/composables/specs/useTokenConfig.spec.js create mode 100644 src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js create mode 100644 src/pages/Claim/Card/specs/ClaimLines.spec.js create mode 100644 src/pages/Claim/Card/specs/ClaimLinesImport.spec.js create mode 100644 src/pages/Claim/Card/specs/ClaimPhoto.spec.js create mode 100644 src/pages/Customer/Payments/specs/CustomerPayments.spec.js create mode 100644 src/pages/Login/specs/Login.spec.js create mode 100644 src/pages/Ticket/Card/specs/TicketBoxing.spec.js create mode 100644 src/pages/Ticket/specs/TicketAdvance.spec.js create mode 100644 src/pages/Wagon/specs/WagonCreate.spec.js create mode 100644 src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js create mode 100644 src/stores/specs/useStateQueryStore.spec.js diff --git a/src/boot/specs/axios.spec.js b/src/boot/specs/axios.spec.js new file mode 100644 index 000000000..b3b6f98c6 --- /dev/null +++ b/src/boot/specs/axios.spec.js @@ -0,0 +1,65 @@ +import { onRequest, onResponseError } from 'src/boot/axios'; +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('src/composables/useSession', () => ({ + useSession: () => ({ + getToken: () => 'DEFAULT_TOKEN', + isLoggedIn: () => vi.fn(), + destroy: () => vi.fn(), + }), +})); + +vi.mock('src/stores/useStateQueryStore', () => ({ + useStateQueryStore: () => ({ + add: () => vi.fn(), + remove: () => vi.fn(), + }), +})); + +describe('Axios boot', () => { + describe('onRequest()', async () => { + it('should set the "Authorization" property on the headers', async () => { + const config = { headers: {} }; + + const resultConfig = onRequest(config); + + expect(resultConfig).toEqual( + expect.objectContaining({ + headers: { + 'Accept-Language': 'en-US', + Authorization: 'DEFAULT_TOKEN', + }, + }) + ); + }); + }); + + describe('onResponseError()', async () => { + it('should call to the Notify plugin with a message error for an status code "500"', async () => { + const error = { + response: { + status: 500, + }, + }; + + const result = onResponseError(error); + expect(result).rejects.toEqual(expect.objectContaining(error)); + }); + + it('should call to the Notify plugin with a message from the response property', async () => { + const error = { + response: { + status: 401, + data: { + error: { + message: 'Invalid user or password', + }, + }, + }, + }; + + const result = onResponseError(error); + expect(result).rejects.toEqual(expect.objectContaining(error)); + }); + }); +}); diff --git a/src/components/VnTable/specs/VnTable.spec.js b/src/components/VnTable/specs/VnTable.spec.js new file mode 100644 index 000000000..162df727d --- /dev/null +++ b/src/components/VnTable/specs/VnTable.spec.js @@ -0,0 +1,47 @@ +import { describe, expect, it, beforeAll, beforeEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import VnTable from 'src/components/VnTable/VnTable.vue'; + +describe('VnTable', () => { + let wrapper; + let vm; + + beforeAll(() => { + wrapper = createWrapper(VnTable, { + propsData: { + columns: [], + }, + }); + vm = wrapper.vm; + }); + + beforeEach(() => (vm.selected = [])); + + describe('handleSelection()', () => { + const rows = [{ $index: 0 }, { $index: 1 }, { $index: 2 }]; + const selectedRows = [{ $index: 1 }]; + it('should add rows to selected when shift key is pressed and rows are added except last one', () => { + vm.handleSelection( + { evt: { shiftKey: true }, added: true, rows: selectedRows }, + rows + ); + expect(vm.selected).toEqual([{ $index: 0 }]); + }); + + it('should not add rows to selected when shift key is not pressed', () => { + vm.handleSelection( + { evt: { shiftKey: false }, added: true, rows: selectedRows }, + rows + ); + expect(vm.selected).toEqual([]); + }); + + it('should not add rows to selected when rows are not added', () => { + vm.handleSelection( + { evt: { shiftKey: true }, added: false, rows: selectedRows }, + rows + ); + expect(vm.selected).toEqual([]); + }); + }); +}); diff --git a/src/components/common/specs/VnChangePassword.spec.js b/src/components/common/specs/VnChangePassword.spec.js new file mode 100644 index 000000000..f5a967bb5 --- /dev/null +++ b/src/components/common/specs/VnChangePassword.spec.js @@ -0,0 +1,70 @@ +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnChangePassword from 'src/components/common/VnChangePassword.vue'; +import { vi, beforeEach, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { Notify } from 'quasar'; + +describe('VnSmsDialog', () => { + let vm; + + beforeAll(() => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [], + }); + vm = createWrapper(VnChangePassword, { + propsData: { + submitFn: vi.fn(), + }, + }).vm; + }); + + beforeEach(() => { + Notify.create = vi.fn(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should notify when new password is empty', async () => { + vm.passwords.newPassword = ''; + vm.passwords.repeatPassword = 'password'; + + await vm.validate(); + expect(Notify.create).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'You must enter a new password', + type: 'negative', + }) + ); + }); + + it("should notify when passwords don't match", async () => { + vm.passwords.newPassword = 'password1'; + vm.passwords.repeatPassword = 'password2'; + await vm.validate(); + expect(Notify.create).toHaveBeenCalledWith( + expect.objectContaining({ + message: `Passwords don't match`, + type: 'negative', + }) + ); + }); + + describe('if passwords match', () => { + it('should call submitFn and emit password', async () => { + vm.passwords.newPassword = 'password'; + vm.passwords.repeatPassword = 'password'; + await vm.validate(); + expect(vm.props.submitFn).toHaveBeenCalledWith('password', undefined); + }); + + it('should call submitFn and emit password and old password', async () => { + vm.passwords.newPassword = 'password'; + vm.passwords.repeatPassword = 'password'; + vm.passwords.oldPassword = 'oldPassword'; + + await vm.validate(); + expect(vm.props.submitFn).toHaveBeenCalledWith('password', 'oldPassword'); + }); + }); +}); diff --git a/src/components/common/specs/VnDiscount.spec.js b/src/components/common/specs/VnDiscount.spec.js new file mode 100644 index 000000000..5d5be61ac --- /dev/null +++ b/src/components/common/specs/VnDiscount.spec.js @@ -0,0 +1,28 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import VnDiscount from 'components/common/vnDiscount.vue'; + +describe('VnDiscount', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(VnDiscount, { + props: { + data: {}, + price: 100, + quantity: 2, + discount: 10, + } + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('total', () => { + it('should calculate total correctly', () => { + expect(vm.total).toBe(180); + }); + }); +}); \ No newline at end of file diff --git a/src/components/common/specs/VnLog.spec.js b/src/components/common/specs/VnLog.spec.js new file mode 100644 index 000000000..53d2732a0 --- /dev/null +++ b/src/components/common/specs/VnLog.spec.js @@ -0,0 +1,134 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnLog from 'src/components/common/VnLog.vue'; + +describe('VnLog', () => { + let vm; + const fakeLogTreeData = [ + { + id: 2, + originFk: 1, + userFk: 18, + action: 'update', + changedModel: 'ClaimObservation', + oldInstance: {}, + newInstance: { + claimFk: 1, + text: 'Waiting for customer', + }, + creationDate: '2023-09-18T12:25:34.000Z', + changedModelId: '1', + changedModelValue: null, + description: null, + user: { + id: 18, + name: 'salesPerson', + nickname: 'salesPersonNick', + image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', + worker: { + id: 18, + userFk: 18, + }, + }, + }, + { + id: 1, + originFk: 1, + userFk: 18, + action: 'update', + changedModel: 'Claim', + oldInstance: { + pickup: null, + }, + newInstance: { + pickup: 'agency', + }, + creationDate: '2023-09-18T12:25:34.000Z', + changedModelId: '1', + changedModelValue: null, + description: null, + user: { + id: 18, + name: 'salesPerson', + nickname: 'salesPersonNick', + image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', + worker: { + id: 18, + userFk: 18, + }, + }, + }, + ]; + const mockValidations = { + Claim: { + locale: { + name: 'reclamación', + }, + }, + ClaimObservation: { + locale: { + name: 'observación', + }, + }, + ClaimDms: { + locale: { + name: 'documento', + }, + }, + ClaimBeginning: { + locale: { + name: 'comienzo', + }, + }, + }; + + beforeAll(async () => { + axios.get.mockImplementation(() => { + return { data: fakeLogTreeData }; + }); + + vm = createWrapper(VnLog, { + global: { + stubs: [], + mocks: {}, + }, + propsData: { + model: 'Claim', + }, + }).vm; + vm.validations = mockValidations; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should correctly set logTree', async () => { + vm.logTree = vm.getLogTree(fakeLogTreeData); + expect(vm.logTree[0].originFk).toEqual(1); + expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson'); + }); + + it('should correctly set the selectedFilters when filtering', () => { + vm.searchInput = '1'; + vm.userSelect = '21'; + vm.checkboxOptions.insert.selected = true; + vm.checkboxOptions.update.selected = true; + + vm.selectFilter('search'); + vm.selectFilter('userSelect'); + + expect(vm.selectedFilters.changedModelId).toEqual('1'); + expect(vm.selectedFilters.userFk).toEqual('21'); + expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] }); + }); + + it('should correctly set the date from', () => { + vm.dateFrom = '18-09-2023'; + vm.selectFilter('date', 'from'); + expect(vm.selectedFilters.creationDate.between).toEqual([ + new Date('2023-09-18T00:00:00.000Z'), + new Date('2023-09-18T21:59:59.999Z'), + ]); + }); +}); diff --git a/src/components/common/specs/VnSmsDialog.spec.js b/src/components/common/specs/VnSmsDialog.spec.js new file mode 100644 index 000000000..0b3473982 --- /dev/null +++ b/src/components/common/specs/VnSmsDialog.spec.js @@ -0,0 +1,58 @@ +import { createWrapper } from 'app/test/vitest/helper'; +import VnSmsDialog from 'components/common/VnSmsDialog.vue'; +import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; + + +describe('VnSmsDialog', () => { + let vm; + const orderId = 1; + const shipped = new Date(); + const phone = '012345678'; + const promise = (response) => {return response;}; + const template = 'minAmount'; + const locale = 'en'; + + beforeAll(() => { + vm = createWrapper(VnSmsDialog, { + propsData: { + data: { + orderId, + shipped + }, + template, + locale, + phone, + promise + } + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('updateMessage()', () => { + it('should update the message value with the correct template and parameters', () => { + vm.updateMessage(); + + 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.`); + }); + }); + + describe('send()', async () => { + it('should send the message', async () => { + vi.spyOn(vm.props, 'promise'); + vm.message = 'Example message'; + const response = { + orderId, + shipped, + destination: phone, + message: vm.message + }; + await vm.send(); + + expect(vm.isLoading).toEqual(false); + expect(vm.props.promise).toHaveBeenCalledWith(response); + }); + }); +}); diff --git a/src/components/specs/CrudModel.spec.js b/src/components/specs/CrudModel.spec.js new file mode 100644 index 000000000..6ce93e59c --- /dev/null +++ b/src/components/specs/CrudModel.spec.js @@ -0,0 +1,120 @@ +import { createWrapper } from 'app/test/vitest/helper'; +import CrudModel from 'components/CrudModel.vue'; +import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; + +describe('CrudModel', () => { + let vm; + beforeAll(() => { + vm = createWrapper(CrudModel, { + global: { + stubs: [ + 'vnPaginate', + 'useState', + 'arrayData', + 'useStateStore', + 'vue-i18n', + ], + mocks: { + validate: vi.fn(), + }, + }, + propsData: { + dataRequired: { + fk: 1, + }, + dataKey: 'crudModelKey', + model: 'crudModel', + url: 'crudModelUrl', + }, + }).vm; + }); + + beforeEach(() => { + vm.fetch([]); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('insert()', () => { + it('should new element in list with index 0 if formData not has data', () => { + vm.insert(); + + expect(vm.formData.length).toEqual(1); + expect(vm.formData[0].fk).toEqual(1); + expect(vm.formData[0].$index).toEqual(0); + }); + }); + + describe('getChanges()', () => { + it('should return correct updates and creates', async () => { + vm.fetch([ + { id: 1, name: 'New name one' }, + { id: 2, name: 'New name two' }, + { id: 3, name: 'Bruce Wayne' }, + ]); + + vm.originalData = [ + { id: 1, name: 'Tony Starks' }, + { id: 2, name: 'Jessica Jones' }, + { id: 3, name: 'Bruce Wayne' }, + ]; + + vm.insert(); + const result = vm.getChanges(); + + const expected = { + creates: [ + { + $index: 3, + fk: 1, + }, + ], + updates: [ + { + data: { + name: 'New name one', + }, + where: { + id: 1, + }, + }, + { + data: { + name: 'New name two', + }, + where: { + id: 2, + }, + }, + ], + }; + + expect(result).toEqual(expected); + }); + }); + + describe('getDifferences()', () => { + it('should return the differences between two objects', async () => { + const obj1 = { + a: 1, + b: 2, + c: 3, + }; + const obj2 = { + a: null, + b: 4, + d: 5, + }; + + const result = vm.getDifferences(obj1, obj2); + + expect(result).toEqual({ + a: null, + b: 4, + d: 5, + }); + }); + }); +}); diff --git a/src/components/specs/Leftmenu.spec.js b/src/components/specs/Leftmenu.spec.js new file mode 100644 index 000000000..10d9d66fb --- /dev/null +++ b/src/components/specs/Leftmenu.spec.js @@ -0,0 +1,94 @@ +import { vi, describe, expect, it, beforeAll } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import Leftmenu from 'components/LeftMenu.vue'; + +import { useNavigationStore } from 'src/stores/useNavigationStore'; + +vi.mock('src/router/modules', () => ({ + default: [ + { + path: '/customer', + name: 'Customer', + meta: { + title: 'customers', + icon: 'vn:client', + }, + menus: { + main: ['CustomerList', 'CustomerCreate'], + card: ['CustomerBasicData'], + }, + children: [ + { + path: '', + name: 'CustomerMain', + children: [ + { + path: 'list', + name: 'CustomerList', + meta: { + title: 'list', + icon: 'view_list', + }, + }, + { + path: 'create', + name: 'CustomerCreate', + meta: { + title: 'createCustomer', + icon: 'vn:addperson', + }, + }, + ], + }, + ], + }, + ], +})); + +describe('Leftmenu', () => { + let vm; + let navigation; + beforeAll(() => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [], + }); + + vm = createWrapper(Leftmenu, { + propsData: { + source: 'main', + }, + }).vm; + + navigation = useNavigationStore(); + navigation.fetchPinned = vi.fn().mockReturnValue(Promise.resolve(true)); + navigation.getModules = vi.fn().mockReturnValue({ + value: [ + { + name: 'customer', + title: 'customer.pageTitles.customers', + icon: 'vn:customer', + module: 'customer', + }, + ], + }); + }); + + it('should return a proper formated object with two child items', async () => { + const expectedMenuItem = [ + { + children: null, + name: 'CustomerList', + title: 'globals.pageTitles.list', + icon: 'view_list', + }, + { + children: null, + name: 'CustomerCreate', + title: 'globals.pageTitles.createCustomer', + icon: 'vn:addperson', + }, + ]; + const firstMenuItem = vm.items[0]; + expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem)); + }); +}); diff --git a/src/components/ui/specs/Paginate.spec.js b/src/components/ui/specs/Paginate.spec.js new file mode 100644 index 000000000..a67dfcdc6 --- /dev/null +++ b/src/components/ui/specs/Paginate.spec.js @@ -0,0 +1,118 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnPaginate from 'src/components/ui/VnPaginate.vue'; + +describe('VnPaginate', () => { + const expectedUrl = '/api/customers'; + const defaultData = [ + { id: 1, name: 'Tony Stark' }, + { id: 2, name: 'Jessica Jones' }, + { id: 3, name: 'Bruce Wayne' }, + ]; + let vm; + beforeAll(() => { + const options = { + attrs: { + url: expectedUrl, + dataKey: 'CustomerList', + order: 'id DESC', + limit: 3, + }, + }; + vm = createWrapper(VnPaginate, options).vm; + }); + + afterEach(() => { + vm.store.data = []; + vm.store.skip = 0; + vm.pagination.page = 1; + vm.hasMoreData = true; + }); + + describe('paginate()', () => { + it('should call to the paginate() method and set the data on the rows property', async () => { + vi.spyOn(vm.arrayData, 'loadMore'); + vm.store.data = defaultData; + + await vm.paginate(); + + expect(vm.arrayData.loadMore).toHaveBeenCalledWith(); + expect(vm.store.data.length).toEqual(3); + }); + + it('should call to the paginate() method and then call it again to paginate', async () => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: defaultData, + }); + vm.store.hasMoreData = true; + await vm.$nextTick(); + + vm.store.data = defaultData; + + await vm.paginate(); + + expect(vm.store.skip).toEqual(3); + expect(vm.store.data.length).toEqual(6); + + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [ + { id: 4, name: 'Peter Parker' }, + { id: 5, name: 'Clark Kent' }, + { id: 6, name: 'Barry Allen' }, + ], + }); + await vm.paginate(); + + expect(vm.store.skip).toEqual(6); + expect(vm.store.data.length).toEqual(9); + }); + }); + + describe('onLoad()', () => { + it('should call to the done() callback and not increment the pagination', async () => { + const index = 1; + const done = vi.fn(); + + await vm.onLoad(index, done); + + expect(vm.pagination.page).toEqual(1); + expect(done).toHaveBeenCalledWith(false); + }); + + it('should increment the pagination and then call to the done() callback', async () => { + expect(vm.pagination.page).toEqual(1); + + const index = 1; + const done = vi.fn(); + vm.store.data = defaultData; + + await vm.onLoad(index, done); + + expect(vm.pagination.page).toEqual(2); + expect(done).toHaveBeenCalledWith(false); + }); + + it('should call to the done() callback with true as argument to finish pagination', async () => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [ + { id: 1, name: 'Tony Stark' }, + { id: 2, name: 'Jessica Jones' }, + ], + }); + + vm.store.data = defaultData; + + expect(vm.pagination.page).toEqual(1); + + const index = 1; + const done = vi.fn(); + + vm.hasMoreData = false; + + await vm.onLoad(index, done); + + expect(vm.pagination.page).toEqual(2); + expect(done).toHaveBeenCalledWith(false); + }); + }); +}); diff --git a/src/components/ui/specs/VnLinkPhone.spec.js b/src/components/ui/specs/VnLinkPhone.spec.js new file mode 100644 index 000000000..a34ef90a5 --- /dev/null +++ b/src/components/ui/specs/VnLinkPhone.spec.js @@ -0,0 +1,50 @@ +import { describe, it, expect, beforeAll, vi } from 'vitest'; +import { axios } from 'app/test/vitest/helper'; +import parsePhone from 'src/filters/parsePhone'; + +describe('parsePhone filter', () => { + beforeAll(async () => { + vi.spyOn(axios, 'get').mockReturnValue({ data: { prefix: '34' } }); + }); + + it('no phone', async () => { + const phone = await parsePhone(null, '34'); + expect(phone).toBe(undefined); + }); + + it("adds prefix +34 if it doesn't have one", async () => { + const phone = await parsePhone('123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('maintains prefix +34 if it is already correct', async () => { + const phone = await parsePhone('+34123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('converts prefix 0034 to +34', async () => { + const phone = await parsePhone('0034123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('converts prefix 34 without symbol to +34', async () => { + const phone = await parsePhone('34123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('replaces incorrect prefix with the correct one', async () => { + const phone = await parsePhone('+44123456789', '34'); + expect(phone).toBe('44123456789'); + }); + + it('adds default prefix on error', async () => { + vi.spyOn(axios, 'get').mockImplementation((url) => { + if (url.includes('Prefixes')) + return Promise.reject(new Error('Network error')); + else if (url.includes('PbxConfigs')) + return Promise.resolve({ data: { defaultPrefix: '39' } }); + }); + const phone = await parsePhone('123456789', '34'); + expect(phone).toBe('39123456789'); + }); +}); diff --git a/src/components/ui/specs/VnSms.spec.js b/src/components/ui/specs/VnSms.spec.js new file mode 100644 index 000000000..e0f8c1868 --- /dev/null +++ b/src/components/ui/specs/VnSms.spec.js @@ -0,0 +1,25 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnSms from 'src/components/ui/VnSms.vue'; + +describe('VnSms', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(VnSms, { + global: { + stubs: ['VnPaginate'], + mocks: {}, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should format number correctly', () => { + const formattedNumber = vm.formatNumber('123456789012'); + expect(formattedNumber).toBe('1234 56789012'); + }); +}); diff --git a/src/composables/specs/downloadFile.spec.js b/src/composables/specs/downloadFile.spec.js new file mode 100644 index 000000000..f53b56b3e --- /dev/null +++ b/src/composables/specs/downloadFile.spec.js @@ -0,0 +1,36 @@ +import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest'; +import { axios } from 'app/test/vitest/helper'; +import { downloadFile } from 'src/composables/downloadFile'; +import { useSession } from 'src/composables/useSession'; +const session = useSession(); +const token = session.getToken(); + +describe('downloadFile', () => { + const baseUrl = 'http://localhost:9000'; + let defaulCreateObjectURL; + + beforeAll(() => { + defaulCreateObjectURL = window.URL.createObjectURL; + window.URL.createObjectURL = vi.fn(() => 'blob:http://localhost:9000/blob-id'); + }); + + afterAll(() => (window.URL.createObjectURL = defaulCreateObjectURL)); + + it('should open a new window to download the file', async () => { + const res = { + data: new Blob(['file content'], { type: 'application/octet-stream' }), + headers: { 'content-disposition': 'attachment; filename="test-file.txt"' }, + }; + vi.spyOn(axios, 'get').mockImplementation((url) => { + if (url == 'Urls/getUrl') return Promise.resolve({ data: baseUrl }); + else if (url.includes('downloadFile')) return Promise.resolve(res); + }); + + await downloadFile(1); + + expect(axios.get).toHaveBeenCalledWith( + `${baseUrl}/api/dms/1/downloadFile?access_token=${token}`, + { responseType: 'blob' } + ); + }); +}); diff --git a/src/composables/specs/getExchange.spec.js b/src/composables/specs/getExchange.spec.js new file mode 100644 index 000000000..dba31458e --- /dev/null +++ b/src/composables/specs/getExchange.spec.js @@ -0,0 +1,45 @@ +import { describe, expect, it, vi } from 'vitest'; +import axios from 'axios'; +import { getExchange } from 'src/composables/getExchange'; + +vi.mock('axios'); + +describe('getExchange()', () => { + it('should return the correct exchange rate', async () => { + axios.get.mockResolvedValue({ + data: { value: 1.2 }, + }); + + const amount = 100; + const currencyFk = 1; + const dated = '2023-01-01'; + const result = await getExchange(amount, currencyFk, dated); + + expect(result).toBe('83.33'); + }); + + it('should return the correct exchange rate with custom decimal places', async () => { + axios.get.mockResolvedValue({ + data: { value: 1.2 }, + }); + + const amount = 100; + const currencyFk = 1; + const dated = '2023-01-01'; + const decimalPlaces = 3; + const result = await getExchange(amount, currencyFk, dated, decimalPlaces); + + expect(result).toBe('83.333'); + }); + + it('should return null if the API call fails', async () => { + axios.get.mockRejectedValue(new Error('Network error')); + + const amount = 100; + const currencyFk = 1; + const dated = '2023-01-01'; + const result = await getExchange(amount, currencyFk, dated); + + expect(result).toBeNull(); + }); +}); diff --git a/src/composables/specs/getTotal.spec.js b/src/composables/specs/getTotal.spec.js new file mode 100644 index 000000000..789e3fbcf --- /dev/null +++ b/src/composables/specs/getTotal.spec.js @@ -0,0 +1,55 @@ +import { vi, describe, expect, it } from 'vitest'; +import { getTotal } from 'src/composables/getTotal'; + +vi.mock('src/filters', () => ({ + toCurrency: vi.fn((value, currency) => `${currency} ${value.toFixed(2)}`), +})); + +describe('getTotal()', () => { + const rows = [ + { amount: 10.5, tax: 2.1 }, + { amount: 20.75, tax: 3.25 }, + { amount: 30.25, tax: 4.75 }, + ]; + + it('should calculate the total for a given key', () => { + const total = getTotal(rows, 'amount'); + expect(total).toBe('61.50'); + }); + + it('should calculate the total with a callback function', () => { + const total = getTotal(rows, null, { cb: (row) => row.amount + row.tax }); + expect(total).toBe('71.60'); + }); + + it('should format the total as currency', () => { + const total = getTotal(rows, 'amount', { currency: 'USD' }); + expect(total).toBe('USD 61.50'); + }); + + it('should format the total as currency with default currency', () => { + const total = getTotal(rows, 'amount', { currency: 'default' }); + expect(total).toBe('undefined 61.50'); + }); + + it('should calculate the total with integer formatting', () => { + const total = getTotal(rows, 'amount', { decimalPlaces: 0 }); + expect(total).toBe('62'); + }); + + it('should calculate the total with custom decimal places', () => { + const total = getTotal(rows, 'amount', { decimalPlaces: 1 }); + expect(total).toBe('61.5'); + }); + + it('should handle rows with missing keys', () => { + const rowsWithMissingKeys = [{ amount: 10.5 }, { amount: 20.75 }, {}]; + const total = getTotal(rowsWithMissingKeys, 'amount'); + expect(total).toBe('31.25'); + }); + + it('should handle empty rows', () => { + const total = getTotal([], 'amount'); + expect(total).toBe('0.00'); + }); +}); diff --git a/src/composables/specs/useAccountShortToStandard.spec.js b/src/composables/specs/useAccountShortToStandard.spec.js new file mode 100644 index 000000000..d24585812 --- /dev/null +++ b/src/composables/specs/useAccountShortToStandard.spec.js @@ -0,0 +1,9 @@ +import { describe, expect, it } from 'vitest'; +import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard'; + +describe('useAccountShortToStandard()', () => { + it('should pad the decimal part with zeros for short numbers', () => { + expect(useAccountShortToStandard('123.45')).toBe('1230000045'); + expect(useAccountShortToStandard('123.')).toBe('1230000000'); + }); +}); diff --git a/src/composables/specs/useAcl.spec.js b/src/composables/specs/useAcl.spec.js new file mode 100644 index 000000000..6cb29984c --- /dev/null +++ b/src/composables/specs/useAcl.spec.js @@ -0,0 +1,110 @@ +import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest'; +import { axios, flushPromises } from 'app/test/vitest/helper'; +import { useAcl } from 'src/composables/useAcl'; + +describe('useAcl', () => { + const acl = useAcl(); + const mockAcls = [ + { + model: 'Address', + property: '*', + accessType: '*', + permission: 'ALLOW', + principalType: 'ROLE', + principalId: 'employee', + }, + { + model: 'Worker', + property: 'holidays', + accessType: 'READ', + permission: 'ALLOW', + principalType: 'ROLE', + principalId: 'employee', + }, + { + model: 'Url', + property: 'getByUser', + accessType: 'READ', + permission: 'ALLOW', + principalType: 'ROLE', + principalId: '$everyone', + }, + { + model: 'TpvTransaction', + property: 'start', + accessType: 'WRITE', + permission: 'ALLOW', + principalType: 'ROLE', + principalId: '$authenticated', + }, + ]; + + beforeAll(async () => { + vi.spyOn(axios, 'get').mockResolvedValue({ data: mockAcls }); + await acl.fetch(); + }); + + afterAll(async () => await flushPromises()); + + describe('hasAny', () => { + it('should return false if no roles matched', async () => { + expect( + acl.hasAny([ + { model: 'Worker', props: 'updateAttributes', accessType: 'WRITE' }, + ]) + ).toBeFalsy(); + }); + + it('should return false if no roles matched', async () => { + expect( + acl.hasAny([{ model: 'Worker', props: 'holidays', accessType: 'READ' }]) + ).toBeTruthy(); + }); + + describe('*', () => { + it('should return true if an acl matched', async () => { + expect( + acl.hasAny([{ model: 'Address', props: '*', accessType: 'WRITE' }]) + ).toBeTruthy(); + }); + + it('should return false if no acls matched', async () => { + expect( + acl.hasAny([{ model: 'Worker', props: '*', accessType: 'READ' }]) + ).toBeFalsy(); + }); + }); + + describe('$authenticated', () => { + it('should return false if no acls matched', async () => { + expect( + acl.hasAny([{ model: 'Url', props: 'getByUser', accessType: '*' }]) + ).toBeFalsy(); + }); + + it('should return true if an acl matched', async () => { + expect( + acl.hasAny([{ model: 'Url', props: 'getByUser', accessType: 'READ' }]) + ).toBeTruthy(); + }); + }); + + describe('$everyone', () => { + it('should return false if no acls matched', async () => { + expect( + acl.hasAny([ + { model: 'TpvTransaction', props: 'start', accessType: 'READ' }, + ]) + ).toBeFalsy(); + }); + + it('should return false if an acl matched', async () => { + expect( + acl.hasAny([ + { model: 'TpvTransaction', props: 'start', accessType: 'WRITE' }, + ]) + ).toBeTruthy(); + }); + }); + }); +}); diff --git a/src/composables/specs/useArrayData.spec.js b/src/composables/specs/useArrayData.spec.js new file mode 100644 index 000000000..d4c5d0949 --- /dev/null +++ b/src/composables/specs/useArrayData.spec.js @@ -0,0 +1,98 @@ +import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest'; +import { axios, flushPromises } from 'app/test/vitest/helper'; +import { useArrayData } from 'composables/useArrayData'; +import { useRouter } from 'vue-router'; +import * as vueRouter from 'vue-router'; + +describe('useArrayData', () => { + const filter = '{"limit":20,"skip":0}'; + const params = { supplierFk: 2 }; + beforeEach(() => { + vi.spyOn(useRouter(), 'replace'); + vi.spyOn(useRouter(), 'push'); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should fetch and repalce url with new params', async () => { + vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] }); + + const arrayData = useArrayData('ArrayData', { url: 'mockUrl' }); + + arrayData.store.userParams = params; + arrayData.fetch({}); + + await flushPromises(); + const routerReplace = useRouter().replace.mock.calls[0][0]; + + expect(axios.get.mock.calls[0][1].params).toEqual({ + filter, + supplierFk: 2, + }); + expect(routerReplace.path).toEqual('mockSection/list'); + expect(JSON.parse(routerReplace.query.params)).toEqual( + expect.objectContaining(params) + ); + }); + + it('Should get data and send new URL without keeping parameters, if there is only one record', async () => { + vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }] }); + + const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} }); + + arrayData.store.userParams = params; + arrayData.fetch({}); + + await flushPromises(); + const routerPush = useRouter().push.mock.calls[0][0]; + + expect(axios.get.mock.calls[0][1].params).toEqual({ + filter, + supplierFk: 2, + }); + expect(routerPush.path).toEqual('mockName/1'); + expect(routerPush.query).toBeUndefined(); + }); + + it('Should get data and send new URL keeping parameters, if you have more than one record', async () => { + vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }, { id: 2 }] }); + + vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ + matched: [], + query: {}, + params: {}, + meta: { moduleName: 'mockName' }, + path: 'mockName/1', + }); + vi.spyOn(vueRouter, 'useRouter').mockReturnValue({ + push: vi.fn(), + replace: vi.fn(), + currentRoute: { + value: { + params: { + id: 1, + }, + meta: { moduleName: 'mockName' }, + matched: [{ path: 'mockName/:id' }], + }, + }, + }); + + const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} }); + + arrayData.store.userParams = params; + arrayData.fetch({}); + + await flushPromises(); + const routerPush = useRouter().push.mock.calls[0][0]; + + expect(axios.get.mock.calls[0][1].params).toEqual({ + filter, + supplierFk: 2, + }); + expect(routerPush.path).toEqual('mockName/'); + expect(routerPush.query.params).toBeDefined(); + }); +}); diff --git a/src/composables/specs/useRole.spec.js b/src/composables/specs/useRole.spec.js new file mode 100644 index 000000000..d0bca5342 --- /dev/null +++ b/src/composables/specs/useRole.spec.js @@ -0,0 +1,73 @@ +import { vi, describe, expect, it } from 'vitest'; +import { axios, flushPromises } from 'app/test/vitest/helper'; +import { useRole } from 'composables/useRole'; +const role = useRole(); + +describe('useRole', () => { + describe('fetch', () => { + it('should call setUser and setRoles of the state with the expected data', async () => { + const rolesData = [ + { + role: { + name: 'salesPerson', + }, + }, + { + role: { + name: 'admin', + }, + }, + ]; + const fetchedUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + }; + const expectedUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + }; + const expectedRoles = ['salesPerson', 'admin']; + vi.spyOn(axios, 'get') + .mockResolvedValueOnce({ + data: { roles: rolesData, user: fetchedUser }, + }) + + vi.spyOn(role.state, 'setUser'); + vi.spyOn(role.state, 'setRoles'); + + role.fetch(); + + await flushPromises(); + + expect(role.state.setUser).toHaveBeenCalledWith(expectedUser); + expect(role.state.setRoles).toHaveBeenCalledWith(expectedRoles); + + role.state.setRoles([]); + }); + }); + + describe('hasAny', () => { + it('should return true if a role matched', async () => { + role.state.setRoles(['admin']); + const hasRole = role.hasAny(['admin']); + + await flushPromises(); + + expect(hasRole).toBe(true); + + role.state.setRoles([]); + }); + + it('should return false if no roles matched', async () => { + const hasRole = role.hasAny(['admin']); + + await flushPromises(); + + expect(hasRole).toBe(false); + }); + }); +}); diff --git a/src/composables/specs/useSession.spec.js b/src/composables/specs/useSession.spec.js new file mode 100644 index 000000000..789b149ec --- /dev/null +++ b/src/composables/specs/useSession.spec.js @@ -0,0 +1,211 @@ +import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest'; +import { axios } from 'app/test/vitest/helper'; +import { useSession } from 'composables/useSession'; +import { useState } from 'composables/useState'; + +const session = useSession(); +const state = useState(); + +describe('session', () => { + describe('getToken / setToken', () => { + it('should return an empty string if no token is found in local or session storage', async () => { + const expectedToken = ''; + + const token = session.getToken(); + + expect(token).toEqual(expectedToken); + }); + + it('should return the token stored in local or session storage', async () => { + const expectedToken = 'myToken'; + const data = { + token: expectedToken, + keepLogin: false, + }; + session.setToken(data); + + const token = session.getToken(); + + expect(token).toEqual(expectedToken); + }); + }); + + describe('destroy', () => { + it('should remove the token from the local storage and set a blank user', async () => { + const previousUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + darkMode: false, + }; + const expectedUser = { + id: 0, + name: '', + nickname: '', + lang: '', + darkMode: null, + }; + let user = state.getUser(); + + localStorage.setItem('token', 'tokenToBeGone'); + state.setUser(previousUser); + + expect(localStorage.getItem('token')).toEqual('tokenToBeGone'); + expect(user.value).toEqual(previousUser); + + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(axios, 'get').mockResolvedValue({ data: true }); + await session.destroy(); + + user = state.getUser(); + expect(localStorage.getItem('token')).toBeNull(); + expect(user.value).toEqual(expectedUser); + }); + }); + + describe( + 'login', + () => { + const expectedUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + userConfig: { + darkMode: false, + }, + }; + const rolesData = [ + { + role: { + name: 'salesPerson', + }, + }, + { + role: { + name: 'admin', + }, + }, + ]; + beforeEach(() => { + vi.spyOn(axios, 'get').mockImplementation((url) => { + if (url === 'VnUsers/acls') return Promise.resolve({ data: [] }); + return Promise.resolve({ + data: { roles: rolesData, user: expectedUser }, + }); + }); + }); + + it('should fetch the user roles and then set token in the sessionStorage', async () => { + const expectedRoles = ['salesPerson', 'admin']; + const expectedToken = 'mySessionToken'; + const expectedTokenMultimedia = 'mySessionTokenMultimedia'; + const keepLogin = false; + + await session.login({ + token: expectedToken, + tokenMultimedia: expectedTokenMultimedia, + keepLogin, + }); + + const roles = state.getRoles(); + const localToken = localStorage.getItem('token'); + const sessionToken = sessionStorage.getItem('token'); + + expect(roles.value).toEqual(expectedRoles); + expect(localToken).toBeNull(); + expect(sessionToken).toEqual(expectedToken); + + await session.destroy(); // this clears token and user for any other test + }); + + it('should fetch the user roles and then set token in the localStorage', async () => { + const expectedRoles = ['salesPerson', 'admin']; + const expectedToken = 'myLocalToken'; + const expectedTokenMultimedia = 'myLocalTokenMultimedia'; + const keepLogin = true; + + await session.login({ + token: expectedToken, + tokenMultimedia: expectedTokenMultimedia, + keepLogin, + }); + + const roles = state.getRoles(); + const localToken = localStorage.getItem('token'); + const sessionToken = sessionStorage.getItem('token'); + + expect(roles.value).toEqual(expectedRoles); + expect(localToken).toEqual(expectedToken); + expect(sessionToken).toBeNull(); + + await session.destroy(); // this clears token and user for any other test + }); + }, + {} + ); + + describe('RenewToken', () => { + const expectedToken = 'myToken'; + const expectedTokenMultimedia = 'myTokenMultimedia'; + const currentDate = new Date(); + beforeAll(() => { + const tokenConfig = { + id: 1, + renewPeriod: 21600, + courtesyTime: 60, + renewInterval: 300, + }; + state.setTokenConfig(tokenConfig); + sessionStorage.setItem('renewPeriod', 1); + }); + it('NOT Should renewToken', async () => { + const data = { + token: expectedToken, + tokenMultimedia: expectedTokenMultimedia, + keepLogin: false, + ttl: 1, + created: Date.now(), + }; + session.setSession(data); + expect(sessionStorage.getItem('keepLogin')).toBeFalsy(); + expect(sessionStorage.getItem('created')).toBeDefined(); + expect(sessionStorage.getItem('ttl')).toEqual(1); + await session.checkValidity(); + expect(sessionStorage.getItem('token')).toEqual(expectedToken); + expect(sessionStorage.getItem('tokenMultimedia')).toEqual( + expectedTokenMultimedia + ); + }); + it('Should renewToken', async () => { + currentDate.setMinutes(currentDate.getMinutes() - 100); + const data = { + token: expectedToken, + tokenMultimedia: expectedTokenMultimedia, + keepLogin: false, + ttl: 1, + created: currentDate, + }; + session.setSession(data); + + vi.spyOn(axios, 'post') + .mockResolvedValueOnce({ + data: { id: '' }, + }) + .mockResolvedValueOnce({ + data: { + id: '', + }, + }); + expect(sessionStorage.getItem('keepLogin')).toBeFalsy(); + expect(sessionStorage.getItem('created')).toBeDefined(); + expect(sessionStorage.getItem('ttl')).toEqual(1); + await session.checkValidity(); + expect(sessionStorage.getItem('token')).not.toEqual(expectedToken); + expect(sessionStorage.getItem('tokenMultimedia')).not.toEqual( + expectedTokenMultimedia + ); + }); + }); +}); diff --git a/src/composables/specs/useTokenConfig.spec.js b/src/composables/specs/useTokenConfig.spec.js new file mode 100644 index 000000000..a25a4abb1 --- /dev/null +++ b/src/composables/specs/useTokenConfig.spec.js @@ -0,0 +1,31 @@ +import { vi, describe, expect, it } from 'vitest'; +import { axios, flushPromises } from 'app/test/vitest/helper'; +import { useTokenConfig } from 'composables/useTokenConfig'; +const tokenConfig = useTokenConfig(); + +describe('useTokenConfig', () => { + describe('fetch', () => { + it('should call setTokenConfig of the state with the expected data', async () => { + const data = { + id: 1, + renewPeriod: 21600, + courtesyTime: 60, + renewInterval: 300, + }; + vi.spyOn(axios, 'get').mockResolvedValueOnce({ + data, + }); + + vi.spyOn(tokenConfig.state, 'setTokenConfig'); + + tokenConfig.fetch(); + + await flushPromises(); + + expect(tokenConfig.state.setTokenConfig).toHaveBeenCalledWith(data); + + const renewPeriod = sessionStorage.getItem('renewPeriod'); + expect(renewPeriod).toEqual(data.renewPeriod); + }); + }); +}); diff --git a/src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js b/src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js new file mode 100644 index 000000000..b208f1704 --- /dev/null +++ b/src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js @@ -0,0 +1,33 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue'; + +describe('ClaimDescriptorMenu', () => { + let vm; + beforeAll(() => { + vm = createWrapper(ClaimDescriptorMenu, { + propsData: { + claim: { + id: 1, + }, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('remove()', () => { + it('should delete the claim', async () => { + vi.spyOn(axios, 'delete').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.remove(); + + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ type: 'positive' }) + ); + }); + }); +}); diff --git a/src/pages/Claim/Card/specs/ClaimLines.spec.js b/src/pages/Claim/Card/specs/ClaimLines.spec.js new file mode 100644 index 000000000..2f2c0e298 --- /dev/null +++ b/src/pages/Claim/Card/specs/ClaimLines.spec.js @@ -0,0 +1,75 @@ +import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import ClaimLines from '/src/pages/Claim/Card/ClaimLines.vue'; + +describe('ClaimLines', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(ClaimLines, { + global: { + stubs: ['FetchData', 'VnPaginate'], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + beforeEach(() => { + vm.claim = { + id: 1, + ticketFk: 1, + }; + vm.store.data = [ + { + id: 1, + quantity: 10, + sale: { + id: 1, + discount: 0, + }, + }, + ]; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('updateDiscount()', () => { + it('should make a POST request to endpoint "updateDiscount"', async () => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + const canceller = new AbortController(); + await vm.updateDiscount({ saleFk: 1, discount: 5, canceller }); + + const expectedData = { salesIds: [1], newDiscount: 5 }; + expect(axios.post).toHaveBeenCalledWith( + 'Tickets/1/updateDiscount', + expectedData, + { + signal: canceller.signal, + } + ); + }); + }); + + describe('onUpdateDiscount()', () => { + it('should make a POST request and then set the discount on the original row', async () => { + vi.spyOn(vm.quasar, 'notify'); + + vm.onUpdateDiscount({ discount: 5, rowIndex: 0 }); + const firstRow = vm.store.data[0]; + + expect(firstRow.sale.discount).toEqual(5); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Discount updated', + type: 'positive', + }) + ); + }); + }); +}); diff --git a/src/pages/Claim/Card/specs/ClaimLinesImport.spec.js b/src/pages/Claim/Card/specs/ClaimLinesImport.spec.js new file mode 100644 index 000000000..d93c96132 --- /dev/null +++ b/src/pages/Claim/Card/specs/ClaimLinesImport.spec.js @@ -0,0 +1,47 @@ +import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import ClaimLinesImport from 'pages/Claim/Card/ClaimLinesImport.vue'; + +describe('ClaimLinesImport', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(ClaimLinesImport, { + global: { + stubs: ['FetchData'], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('importLines()', () => { + it('should make a POST request and then call to the quasar notify() method', async () => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + vm.selected = [{ id: 1, saleFk: 1, claimFk: 1 }]; + + vm.route.params.id = 1; + + await vm.importLines(); + const expectedData = [{ saleFk: 1, claimFk: 1 }]; + + expect(axios.post).toHaveBeenCalledWith('ClaimBeginnings', expectedData, { + signal: expect.any(Object), + }); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Lines added to claim', + type: 'positive', + }) + ); + expect(vm.canceller).toEqual(null); + }); + }); +}); diff --git a/src/pages/Claim/Card/specs/ClaimPhoto.spec.js b/src/pages/Claim/Card/specs/ClaimPhoto.spec.js new file mode 100644 index 000000000..c38852af1 --- /dev/null +++ b/src/pages/Claim/Card/specs/ClaimPhoto.spec.js @@ -0,0 +1,114 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import ClaimPhoto from 'pages/Claim/Card/ClaimPhoto.vue'; + +describe('ClaimPhoto', () => { + let vm; + + const claimMock = { + claimDms: [ + { + dmsFk: 1, + dms: { + contentType: 'contentType', + }, + }, + ], + client: { + id: '1', + }, + }; + beforeAll(() => { + vm = createWrapper(ClaimPhoto, { + global: { + stubs: ['FetchData', 'vue-i18n'], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('deleteDms()', () => { + it('should delete dms and call quasar notify', async () => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.deleteDms({ index: 0 }); + + expect(axios.post).toHaveBeenCalledWith( + `ClaimDms/${claimMock.claimDms[0].dmsFk}/removeFile` + ); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ type: 'positive' }) + ); + }); + }); + + describe('viewDeleteDms()', () => { + it('should call quasar dialog', async () => { + vi.spyOn(vm.quasar, 'dialog'); + + await vm.viewDeleteDms(1); + + expect(vm.quasar.dialog).toHaveBeenCalledWith( + expect.objectContaining({ + componentProps: { + title: 'This file will be deleted', + icon: 'delete', + data: { index: 1 }, + promise: vm.deleteDms + }, + }) + ); + }); + }); + + describe('setClaimDms()', () => { + it('should assign claimDms and client from data', async () => { + await vm.setClaimDms(claimMock); + + expect(vm.claimDms).toEqual([ + { + dmsFk: 1, + dms: { + contentType: 'contentType', + }, + isVideo: false, + url: '/api/Claims/1/downloadFile?access_token=', + }, + ]); + + expect(vm.client).toEqual(claimMock.client); + }); + }); + + describe('create()', () => { + it('should upload file and call quasar notify', async () => { + const files = [{ name: 'firstFile' }]; + + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + vi.spyOn(vm.claimDmsRef, 'fetch'); + + await vm.create(files); + + expect(axios.post).toHaveBeenCalledWith( + 'claims/1/uploadFile', + new FormData(), + expect.objectContaining({ + params: expect.objectContaining({ hasFile: false }), + }) + ); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ type: 'positive' }) + ); + + expect(vm.claimDmsRef.fetch).toHaveBeenCalledOnce(); + }); + }); +}); diff --git a/src/pages/Customer/Payments/specs/CustomerPayments.spec.js b/src/pages/Customer/Payments/specs/CustomerPayments.spec.js new file mode 100644 index 000000000..466a544b4 --- /dev/null +++ b/src/pages/Customer/Payments/specs/CustomerPayments.spec.js @@ -0,0 +1,38 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import CustomerPayments from 'src/pages/Customer/Payments/CustomerPayments.vue'; + +describe('CustomerPayments', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(CustomerPayments, { + global: { + stubs: ['VnPaginate'], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('confirmTransaction()', () => { + it('should make a POST request and then call to quasar notify method', async () => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.confirmTransaction({ id: 1 }); + + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Payment confirmed', + type: 'positive', + }) + ); + }); + }); +}); diff --git a/src/pages/Login/specs/Login.spec.js b/src/pages/Login/specs/Login.spec.js new file mode 100644 index 000000000..e90a8ee53 --- /dev/null +++ b/src/pages/Login/specs/Login.spec.js @@ -0,0 +1,49 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import Login from 'pages/Login/LoginMain.vue'; + +describe('Login', () => { + let vm; + beforeAll(() => { + vm = createWrapper(Login).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should successfully set the token into session', async () => { + const expectedUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + userConfig: { + darkMode: false, + }, + }; + vi.spyOn(axios, 'post').mockResolvedValueOnce({ data: { token: 'token' } }); + vi.spyOn(axios, 'get').mockImplementation((url) => { + if (url === 'VnUsers/acls') return Promise.resolve({ data: [] }); + return Promise.resolve({ + data: { + roles: [], + user: expectedUser, + multimediaToken: { id: 'multimediaToken' }, + }, + }); + }); + + expect(vm.session.getToken()).toEqual(''); + + await vm.onSubmit(); + + expect(vm.session.getToken()).toEqual('token'); + await vm.session.destroy(); + }); + + it('should not set the token into session if any error occurred', async () => { + vi.spyOn(axios, 'post').mockReturnValue({ data: null }); + await vm.onSubmit(); + }); +}); diff --git a/src/pages/Ticket/Card/specs/TicketBoxing.spec.js b/src/pages/Ticket/Card/specs/TicketBoxing.spec.js new file mode 100644 index 000000000..8fd62d8c2 --- /dev/null +++ b/src/pages/Ticket/Card/specs/TicketBoxing.spec.js @@ -0,0 +1,50 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import TicketBoxing from 'pages/Ticket/Card/TicketBoxing.vue'; + +// #4836 - Investigate how to test q-drawer outside +// q-layout or how to teleport q-drawer inside +describe('TicketBoxing', () => { + let vm; + beforeAll(() => { + vm = createWrapper(TicketBoxing).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('getVideoList()', () => { + it('should when response videoList use to list', async () => { + const expeditionId = 1; + const timed = { + min: 1, + max: 2, + }; + const videoList = ['2022-01-01T01-01-00.mp4', '2022-02-02T02-02-00.mp4', '2022-03-03T03-03-00.mp4']; + + vi.spyOn(axios, 'get').mockResolvedValue({ data: videoList }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.getVideoList(expeditionId, timed); + + expect(vm.videoList.length).toEqual(videoList.length); + expect(vm.slide).toEqual(videoList.reverse()[0]); + }); + + it('should if not have video show notify', async () => { + const expeditionId = 1; + const timed = { + min: 1, + max: 2, + }; + + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.getVideoList(expeditionId, timed); + + expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'negative' })); + }); + }); +}); diff --git a/src/pages/Ticket/specs/TicketAdvance.spec.js b/src/pages/Ticket/specs/TicketAdvance.spec.js new file mode 100644 index 000000000..ab1a47544 --- /dev/null +++ b/src/pages/Ticket/specs/TicketAdvance.spec.js @@ -0,0 +1,120 @@ +import { vi, describe, expect, it, beforeAll, afterEach, beforeEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import TicketAdvance from 'pages/Ticket/TicketAdvance.vue'; +import { Notify } from 'quasar'; +import { nextTick } from 'vue'; + +describe('TicketAdvance', () => { + let wrapper; + let vm; + + beforeAll(() => { + vi.spyOn(axios, 'get').mockImplementation(() => ({ data: [] })); + wrapper = createWrapper(TicketAdvance); + vm = wrapper.vm; + vi.spyOn(vm.vnTableRef, 'reload').mockImplementation(() => vi.fn()); + vm.vnTableRef.value = { params: {} }; + }); + beforeEach(() => { + Notify.create = vi.fn(); + }); + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('requestComponentUpdate()', () => { + const mockTicket = { + futureId: 1, + futureClientFk: 1, + nickname: 'test', + futureAddressFk: 1, + futureAgencyModeFk: 1, + futureWarehouseFk: 1, + futureCompanyFk: 1, + landed: '2023-01-02', + zoneFk: 1, + }; + const mockParams = { + clientFk: 1, + nickname: 'test', + agencyModeFk: 1, + addressFk: 1, + zoneFk: 1, + warehouseFk: 1, + companyFk: 1, + landed: '2023-01-02', + shipped: '2023-01-01', + isDeleted: false, + isWithoutNegatives: false, + newTicket: undefined, + keepPrice: true, + }; + const queryResult = 'tickets/1/componentUpdate'; + + it('should return query and params when ticket has no landed', async () => { + vm.vnTableRef.params.dateToAdvance = '2023-01-01'; + await nextTick(); + + const mockLanded = { landed: '2023-01-02', zoneFk: 1 }; + vi.spyOn(vm, 'getLanded').mockResolvedValue(mockLanded); + + const { query, params } = await vm.requestComponentUpdate(mockTicket, false); + + expect(query).toBe(queryResult); + expect(params).toEqual(mockParams); + }); + + it('should return query and params when ticket has landed', async () => { + const { query, params } = await vm.requestComponentUpdate(mockTicket, false); + + expect(query).toBe(queryResult); + expect(params).toEqual(mockParams); + }); + }); + + describe('moveTicketsAdvance()', () => { + it('should move tickets and notify success', async () => { + const tickets = [ + { + id: 1, + futureId: 2, + futureShipped: '2023-01-01', + shipped: '2023-01-02', + workerFk: 1, + }, + { + id: 2, + futureId: 3, + futureShipped: '2023-01-01', + shipped: '2023-01-02', + workerFk: 1, + }, + ]; + vm.selectedTickets = tickets; + vi.spyOn(axios, 'post').mockResolvedValue({}); + await vm.moveTicketsAdvance(); + + expect(axios.post).toHaveBeenCalledOnce('Tickets/merge', { + tickets: [ + { + originId: 2, + destinationId: 1, + originShipped: '2023-01-01', + destinationShipped: '2023-01-02', + workerFk: 1, + }, + { + originId: 3, + destinationId: 2, + originShipped: '2023-01-01', + destinationShipped: '2023-01-02', + workerFk: 1, + }, + ], + }); + expect(vm.vnTableRef.reload).toHaveBeenCalled(); + expect(Notify.create).toHaveBeenCalled(); + expect(vm.selectedTickets).toEqual([]); + }); + }); +}); diff --git a/src/pages/Wagon/specs/WagonCreate.spec.js b/src/pages/Wagon/specs/WagonCreate.spec.js new file mode 100644 index 000000000..f195c183f --- /dev/null +++ b/src/pages/Wagon/specs/WagonCreate.spec.js @@ -0,0 +1,97 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import WagonCreate from 'pages/Wagon/WagonCreate.vue'; + +describe('WagonCreate', () => { + let vmEdit, vmCreate; + const entityId = 1; + + beforeAll(() => { + vmEdit = createWrapper(WagonCreate, { + propsData: { + id: entityId, + }, + }).vm; + vmCreate = createWrapper(WagonCreate).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('onSubmit()', () => { + it('should create a wagon', async () => { + vi.spyOn(axios, 'patch').mockResolvedValue({ data: true }); + vmCreate.wagon = { + label: 1234, + plate: 'MOCK PLATE', + volume: 50, + typeFk: 1, + }; + + await vmCreate.onSubmit(); + + expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmCreate.wagon); + }); + + it('should update a wagon', async () => { + vi.spyOn(axios, 'patch').mockResolvedValue({ data: true }); + vmEdit.wagon = { + id: entityId, + label: 1234, + plate: 'MOCK PLATE', + volume: 50, + typeFk: 1, + }; + + await vmEdit.onSubmit(); + + expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmEdit.wagon); + }); + }); + + describe('onReset()', () => { + it('should reset wagon if have id', async () => { + vmEdit.originalData = { + label: 1234, + plate: 'Original', + volume: 200, + typeFk: 1, + }; + vmEdit.wagon = { + label: 4321, + plate: 'Edited', + volume: 50, + typeFk: 2, + }; + + await vmEdit.onReset(); + + expect(vmEdit.wagon).toEqual(vmEdit.originalData); + }); + + it('should reset wagon if not have id', async () => { + vmCreate.wagon = { + label: 4321, + plate: 'Edited', + volume: 50, + typeFk: 2, + }; + + await vmCreate.onReset(); + + expect(vmCreate.wagon).toEqual({}); + }); + }); + + describe('fetch()', () => { + it('should fetch data', async () => { + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + + await vmEdit.fetch(); + + expect(axios.get).toHaveBeenCalledWith(`WagonTypes`); + expect(axios.get).toHaveBeenCalledWith(`Wagons/${entityId}`); + }); + }); +}); diff --git a/src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js b/src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js new file mode 100644 index 000000000..35ce91e61 --- /dev/null +++ b/src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js @@ -0,0 +1,33 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import WorkerNotificationsManager from 'src/pages/Worker/Card/WorkerNotificationsManager.vue'; +import { ref } from 'vue'; + +describe('WorkerNotificationsManager', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(WorkerNotificationsManager, { + global: { + stubs: ['CrudModel'], + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('swapEntry()', () => { + it('should swap notification', async () => { + const from = ref(new Map()); + const to = ref(new Map()); + from.value.set(1, { notificationFk: 1 }); + to.value.set(2, { notificationFk: 2 }); + + await vm.swapEntry(from.value, to.value, 1); + expect(to.value.size).toBe(2); + expect(from.value.size).toBe(0); + }); + }); +}); diff --git a/src/stores/specs/useStateQueryStore.spec.js b/src/stores/specs/useStateQueryStore.spec.js new file mode 100644 index 000000000..ab3afb007 --- /dev/null +++ b/src/stores/specs/useStateQueryStore.spec.js @@ -0,0 +1,58 @@ +import { describe, expect, it, beforeEach, beforeAll } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; + +import { useStateQueryStore } from 'src/stores/useStateQueryStore'; + +describe('useStateQueryStore', () => { + beforeAll(() => { + createWrapper({}, {}); + }); + + const stateQueryStore = useStateQueryStore(); + const { add, isLoading, remove, reset } = useStateQueryStore(); + const firstQuery = { url: 'myQuery' }; + + function getQueries() { + return stateQueryStore.queries; + } + + beforeEach(() => { + reset(); + expect(getQueries().size).toBeFalsy(); + }); + + it('should add two queries', async () => { + expect(getQueries().size).toBeFalsy(); + add(firstQuery); + + expect(getQueries().size).toBeTruthy(); + expect(getQueries().has(firstQuery)).toBeTruthy(); + + add(); + expect(getQueries().size).toBe(2); + }); + + it('should add and remove loading state', async () => { + expect(isLoading().value).toBeFalsy(); + add(firstQuery); + expect(isLoading().value).toBeTruthy(); + remove(firstQuery); + expect(isLoading().value).toBeFalsy(); + }); + + it('should add and remove query', async () => { + const secondQuery = { ...firstQuery }; + const thirdQuery = { ...firstQuery }; + + add(firstQuery); + add(secondQuery); + + const beforeCount = getQueries().size; + add(thirdQuery); + expect(getQueries().has(thirdQuery)).toBeTruthy(); + + remove(thirdQuery); + expect(getQueries().has(thirdQuery)).toBeFalsy(); + expect(getQueries().size).toBe(beforeCount); + }); +});