diff --git a/.eslintrc.js b/.eslintrc.js index d2fe165a5..09dc09c1e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -57,6 +57,7 @@ module.exports = { // add your custom rules here rules: { 'prefer-promise-reject-errors': 'off', + 'no-unused-vars': 'warn', // allow debugger during development only 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', diff --git a/cypress.config.js b/cypress.config.js index 59276f815..31aad6a86 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -2,7 +2,7 @@ const { defineConfig } = require('cypress'); module.exports = defineConfig({ e2e: { - baseUrl: 'http://localhost:8080/', + baseUrl: 'http://localhost:9000/', fixturesFolder: 'test/cypress/fixtures', screenshotsFolder: 'test/cypress/screenshots', supportFile: 'test/cypress/support/index.js', diff --git a/src/components/common/SendEmailDialog.vue b/src/components/common/SendEmailDialog.vue index f8d80ac06..0db0af056 100644 --- a/src/components/common/SendEmailDialog.vue +++ b/src/components/common/SendEmailDialog.vue @@ -34,19 +34,26 @@ async function confirm() { - {{ t('sendEmailNotification') }} + {{ + t('Send email notification: Send email notification') + }} - {{ t('notifyAddress') }} + {{ t('The notification will be sent to the following address') }} - + @@ -59,14 +66,7 @@ async function confirm() { -{ - "en": { - "sendEmailNotification": "Send email notification", - "notifyAddress": "The notification will be sent to the following address" - }, - "es": { - "sendEmailNotification": "Enviar notificación por correo", - "notifyAddress": "La notificación se enviará a la siguiente dirección" - } -} + es: + Send email notification: Enviar notificación por correo, + The notification will be sent to the following address: La notificación se enviará a la siguiente dirección diff --git a/src/components/ui/Confirm.vue b/src/components/ui/VnConfirm.vue similarity index 53% rename from src/components/ui/Confirm.vue rename to src/components/ui/VnConfirm.vue index bd261f7b4..1b92477eb 100644 --- a/src/components/ui/Confirm.vue +++ b/src/components/ui/VnConfirm.vue @@ -3,30 +3,42 @@ import { ref } from 'vue'; import { useDialogPluginComponent } from 'quasar'; import { useI18n } from 'vue-i18n'; -const $props = defineProps({ +const { t } = useI18n(); + +const props = defineProps({ + icon: { + type: String, + default: null, + }, question: { type: String, - default: '', + default: null, }, message: { type: String, - default: '', + default: null, }, }); defineEmits(['confirm', ...useDialogPluginComponent.emits]); const { dialogRef, onDialogOK } = useDialogPluginComponent(); -const { t } = useI18n(); -const question = ref($props.question); -const message = ref($props.question); +const question = props.question || t('question'); +const message = props.message || t('message'); const isLoading = ref(false); - + + {{ message }} @@ -36,7 +48,12 @@ const isLoading = ref(false); - + @@ -47,3 +64,15 @@ const isLoading = ref(false); min-width: 350px; } + + + "en": { + "question": "Are you sure you want to continue?", + "message": "Confirm" + } + + "es": { + "question": "¿Seguro que quieres continuar?", + "message": "Confirmar" + } + diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js index cd0be31ba..d9df1342f 100644 --- a/src/i18n/en/index.js +++ b/src/i18n/en/index.js @@ -241,6 +241,7 @@ export default { summary: 'Summary', basicData: 'Basic Data', rma: 'RMA', + photos: 'Photos', }, list: { customer: 'Customer', @@ -294,6 +295,11 @@ export default { picked: 'Picked', returnOfMaterial: 'Return of material authorization (RMA)', }, + photo: { + fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}', + noData: 'There are no images/videos, click here or drag and drop the file', + dragDrop: 'Drag and drop it here', + }, }, invoiceOut: { pageTitles: { diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js index 7de7c5263..bdbee15a2 100644 --- a/src/i18n/es/index.js +++ b/src/i18n/es/index.js @@ -13,7 +13,7 @@ export default { darkMode: 'Modo oscuro', logOut: 'Cerrar sesión', dataSaved: 'Datos guardados', - dataDeleted: 'Data deleted', + dataDeleted: 'Datos eliminados', add: 'Añadir', create: 'Crear', save: 'Guardar', @@ -240,6 +240,7 @@ export default { summary: 'Resumen', basicData: 'Datos básicos', rma: 'RMA', + photos: 'Fotos', }, list: { customer: 'Cliente', @@ -293,6 +294,12 @@ export default { picked: 'Recogida', returnOfMaterial: 'Autorización de retorno de materiales (RMA)', }, + photo: { + fileDescription: + 'Reclamacion ID {claimId} del cliente {clientName} id {clientId}', + noData: 'No hay imágenes/videos, haz click aquí o arrastra y suelta el archivo', + dragDrop: 'Arrástralo y sueltalo aquí', + }, }, invoiceOut: { pageTitles: { diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue index df6faf323..f66af86cb 100644 --- a/src/pages/Claim/Card/ClaimCard.vue +++ b/src/pages/Claim/Card/ClaimCard.vue @@ -18,4 +18,4 @@ const state = useState(); - \ No newline at end of file + diff --git a/src/pages/Claim/Card/ClaimPhoto.vue b/src/pages/Claim/Card/ClaimPhoto.vue new file mode 100644 index 000000000..ba974ca87 --- /dev/null +++ b/src/pages/Claim/Card/ClaimPhoto.vue @@ -0,0 +1,376 @@ + + + setClaimDms(data)" + limit="20" + auto-load + ref="claimDmsRef" + /> + (dmsType = data)" + auto-load + /> + (config = data)" + auto-load + /> + + + + + {{ t('claim.photo.dragDrop') }} + + + + + + + {{ t('claim.photo.noData') }} + + + + + + + Video + + + + + + + + + + + + + + + + {{ t('globals.add') }} + + + + + + + + + + + {{ t('globals.add') }} + + + + + + + + + + + + + + + + + + + + + + + + + + es: + This file will be deleted: Este archivo va a ser borrado + diff --git a/src/pages/Claim/Card/ClaimRma.vue b/src/pages/Claim/Card/ClaimRma.vue index 8461eb6bd..6f734b917 100644 --- a/src/pages/Claim/Card/ClaimRma.vue +++ b/src/pages/Claim/Card/ClaimRma.vue @@ -6,7 +6,7 @@ import { useRoute } from 'vue-router'; import axios from 'axios'; import Paginate from 'src/components/PaginateData.vue'; import FetchData from 'components/FetchData.vue'; -import TeleportSlot from 'components/ui/TeleportSlot'; +import TeleportSlot from 'components/ui/TeleportSlot.vue'; import { toDate } from 'src/filters'; const quasar = useQuasar(); @@ -90,22 +90,38 @@ function hide() { - {{ t('claim.rma.user') }} - {{ row.worker.user.name }} + {{ + t('claim.rma.user') + }} + {{ + row.worker.user.name + }} - {{ t('claim.rma.created') }} + {{ + t('claim.rma.created') + }} - {{ toDate(row.created, { timeStyle: 'medium' }) }} + {{ + toDate(row.created, { + timeStyle: 'medium', + }) + }} - + {{ t('globals.remove') }} @@ -124,7 +140,13 @@ function hide() { - + diff --git a/src/router/modules/claim.js b/src/router/modules/claim.js index 1c1cfb50e..597d3e61b 100644 --- a/src/router/modules/claim.js +++ b/src/router/modules/claim.js @@ -11,7 +11,7 @@ export default { redirect: { name: 'ClaimMain' }, menus: { main: ['ClaimList', 'ClaimRmaList'], - card: ['ClaimBasicData', 'ClaimRma'], + card: ['ClaimBasicData', 'ClaimRma', 'ClaimPhotos'], }, children: [ { @@ -76,6 +76,15 @@ export default { }, component: () => import('src/pages/Claim/Card/ClaimRma.vue'), }, + { + name: 'ClaimPhotos', + path: 'photos', + meta: { + title: 'photos', + icon: 'image', + }, + component: () => import('src/pages/Claim/Card/ClaimPhoto.vue'), + }, ], }, ], diff --git a/test/cypress/fixtures/image.jpg b/test/cypress/fixtures/image.jpg new file mode 100644 index 000000000..83052dea6 Binary files /dev/null and b/test/cypress/fixtures/image.jpg differ diff --git a/test/cypress/integration/claimPhoto.spec.js b/test/cypress/integration/claimPhoto.spec.js new file mode 100755 index 000000000..e5ea2ce25 --- /dev/null +++ b/test/cypress/integration/claimPhoto.spec.js @@ -0,0 +1,55 @@ +/// +describe('ClaimPhoto', () => { + beforeEach(() => { + const claimId = 1; + cy.login('developer'); + cy.visit(`/#/claim/${claimId}/photos`); + }); + + it('should add new file', () => { + cy.get('label > .q-btn').click(); + cy.get('label > .q-btn input').selectFile('test/cypress/fixtures/image.jpg', { + force: true, + }); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('should add new file with drag and drop', () => { + cy.get('.container').selectFile('test/cypress/fixtures/image.jpg', { + action: 'drag-drop', + }); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('should open first image dialog change to second and close', () => { + cy.get( + ':nth-child(1) > .q-card > .q-img > .q-img__container > .q-img__image' + ).click(); + cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( + 'be.visible' + ); + + cy.get('.q-carousel__control > .q-btn > .q-btn__content > .q-icon').click(); + + cy.get( + '.q-dialog__inner > .q-toolbar > .q-btn > .q-btn__content > .q-icon' + ).click(); + cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( + 'not.be.visible' + ); + }); + + it('should remove third and fourth file', () => { + cy.get( + '.multimediaParent > :nth-child(3) > .q-btn > .q-btn__content > .q-icon' + ).click(); + cy.get('.q-btn--standard > .q-btn__content > .block').click(); + cy.get('.q-notification__message').should('have.text', 'Data deleted'); + + cy.get( + '.multimediaParent > :nth-child(3) > .q-btn > .q-btn__content > .q-icon' + ).click(); + cy.get('.q-btn--standard > .q-btn__content > .block').click(); + cy.get('.q-notification__message').should('have.text', 'Data deleted'); + }); +}); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 4ab3ae23f..a3a61c423 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -28,7 +28,7 @@ // Imports Quasar Cypress AE predefined commands // import { registerCommands } from '@quasar/quasar-app-extension-testing-e2e-cypress'; Cypress.Commands.add('login', (user) => { - cy.visit('/#/login'); + //cy.visit('/#/login'); cy.request({ method: 'POST', url: '/api/accounts/login', diff --git a/test/vitest/__tests__/pages/Claims/ClaimDescriptorMenu.spec.js b/test/vitest/__tests__/pages/Claims/ClaimDescriptorMenu.spec.js index 3a2ec46d7..a1c3a38ed 100644 --- a/test/vitest/__tests__/pages/Claims/ClaimDescriptorMenu.spec.js +++ b/test/vitest/__tests__/pages/Claims/ClaimDescriptorMenu.spec.js @@ -25,7 +25,9 @@ describe('ClaimDescriptorMenu', () => { await vm.deleteClaim(); - expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'positive' })); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ type: 'positive' }) + ); }); }); }); diff --git a/test/vitest/__tests__/pages/Claims/ClaimPhoto.spec.js b/test/vitest/__tests__/pages/Claims/ClaimPhoto.spec.js new file mode 100644 index 000000000..33862445d --- /dev/null +++ b/test/vitest/__tests__/pages/Claims/ClaimPhoto.spec.js @@ -0,0 +1,113 @@ +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', 'TeleportSlot', '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(0); + + expect(axios.post).toHaveBeenCalledWith( + `ClaimDms/${claimMock.claimDms[0].dmsFk}/removeFile` + ); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ type: 'positive' }) + ); + expect(vm.claimDms).toEqual([]); + }); + }); + + 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: { + message: 'This file will be deleted', + icon: 'delete', + }, + }) + ); + }); + }); + + 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/test/vitest/helper.js b/test/vitest/helper.js index 13987080d..c22d9b0c9 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -17,13 +17,44 @@ const mockPush = vi.fn(); vi.mock('vue-router', () => ({ useRouter: () => ({ push: mockPush, - currentRoute: { value: 'myCurrentRoute' }, + currentRoute: { + value: { + params: { + id: 1, + }, + }, + }, }), useRoute: () => ({ matched: [], }), })); +class FormDataMock { + append() { + vi.fn(); + } + delete() { + vi.fn(); + } + get() { + vi.fn(); + } + getAll() { + vi.fn(); + } + has() { + vi.fn(); + } + set() { + vi.fn(); + } + forEach() { + vi.fn(); + } +} +global.FormData = FormDataMock; + export function createWrapper(component, options) { const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false });