diff --git a/src/components/common/__tests__/VnNotes.spec.js b/src/components/common/__tests__/VnNotes.spec.js index 2603bf03c..ea595060a 100644 --- a/src/components/common/__tests__/VnNotes.spec.js +++ b/src/components/common/__tests__/VnNotes.spec.js @@ -1,16 +1,6 @@ -import { - describe, - it, - expect, - vi, - beforeAll, - afterEach, - beforeEach, - afterAll, -} from 'vitest'; +import { describe, it, expect, vi, afterEach, beforeEach, afterAll } from 'vitest'; import { createWrapper, axios } from 'app/test/vitest/helper'; import VnNotes from 'src/components/ui/VnNotes.vue'; -import vnDate from 'src/boot/vnDate'; describe('VnNotes', () => { let vm; @@ -18,6 +8,7 @@ describe('VnNotes', () => { let spyFetch; let postMock; let patchMock; + let deleteMock; let expectedInsertBody; let expectedUpdateBody; const defaultOptions = { @@ -57,6 +48,7 @@ describe('VnNotes', () => { beforeEach(() => { postMock = vi.spyOn(axios, 'post'); patchMock = vi.spyOn(axios, 'patch'); + deleteMock = vi.spyOn(axios, 'delete'); }); afterEach(() => { @@ -153,4 +145,16 @@ describe('VnNotes', () => { ); }); }); + + describe('delete', () => { + it('Should call axios.delete with url and vnPaginateRef.fetch', async () => { + generateWrapper(); + createSpyFetch(); + + await vm.deleteNote({ id: 1 }); + + expect(deleteMock).toHaveBeenCalledWith(`${vm.$props.url}/1`); + expect(spyFetch).toHaveBeenCalled(); + }); + }); }); diff --git a/src/components/ui/VnMoreOptions.vue b/src/components/ui/VnMoreOptions.vue index 984e2b64f..bc81233d5 100644 --- a/src/components/ui/VnMoreOptions.vue +++ b/src/components/ui/VnMoreOptions.vue @@ -9,7 +9,7 @@ data-cy="descriptor-more-opts" > <QTooltip> - {{ $t('components.cardDescriptor.moreOptions') }} + {{ $t('components.vnDescriptor.moreOptions') }} </QTooltip> <QMenu ref="menuRef" data-cy="descriptor-more-opts-menu"> <QList data-cy="descriptor-more-opts_list"> diff --git a/src/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue index b7e6ccbec..9cedbccfa 100644 --- a/src/components/ui/VnNotes.vue +++ b/src/components/ui/VnNotes.vue @@ -18,10 +18,10 @@ import VnInput from 'components/common/VnInput.vue'; const emit = defineEmits(['onFetch']); -const $attrs = useAttrs(); - -const isRequired = computed(() => { - return Object.keys($attrs).includes('required'); +const originalAttrs = useAttrs(); +const $attrs = computed(() => { + const { required, deletable, ...rest } = originalAttrs; + return rest; }); const $props = defineProps({ @@ -53,6 +53,11 @@ function handleClick(e) { else insert(); } +async function deleteNote(e) { + await axios.delete(`${$props.url}/${e.id}`); + await vnPaginateRef.value.fetch(); +} + async function insert() { if (!newNote.text || ($props.selectType && !newNote.observationTypeFk)) return; @@ -157,7 +162,7 @@ const handleObservationTypes = (data) => { v-model="newNote.observationTypeFk" option-label="description" style="flex: 0.15" - :required="isRequired" + :required="'required' in originalAttrs" @keyup.enter.stop="insert" /> <VnInput @@ -165,11 +170,10 @@ const handleObservationTypes = (data) => { type="textarea" :label="$props.justInput && newNote.text ? '' : t('Add note here...')" filled - size="lg" autogrow autofocus @keyup.enter.stop="handleClick" - :required="isRequired" + :required="'required' in originalAttrs" clearable > <template #append> @@ -239,6 +243,21 @@ const handleObservationTypes = (data) => { </QBadge> </div> <span v-text="toDateHourMin(note.created)" /> + <div> + <QIcon + v-if="'deletable' in originalAttrs" + name="delete" + size="sm" + class="cursor-pointer" + color="primary" + @click="deleteNote(note)" + data-cy="notesRemoveNoteBtn" + > + <QTooltip> + {{ t('ticketNotes.removeNote') }} + </QTooltip> + </QIcon> + </div> </div> </QCardSection> <QCardSection class="q-pa-xs q-my-none q-py-none"> diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index 06996c2c1..e0d9928f9 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -134,7 +134,7 @@ const columns = computed(() => [ const STATE_COLOR = { pending: 'bg-warning', - managed: 'bg-info', + loses: 'bg-negative', resolved: 'bg-positive', }; </script> diff --git a/src/pages/Customer/Card/CustomerBalance.vue b/src/pages/Customer/Card/CustomerBalance.vue index 11db92eab..15f80b2f6 100644 --- a/src/pages/Customer/Card/CustomerBalance.vue +++ b/src/pages/Customer/Card/CustomerBalance.vue @@ -20,6 +20,7 @@ import VnFilter from 'components/VnTable/VnFilter.vue'; import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; +import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; const { openConfirmationModal } = useVnConfirm(); const { sendEmail, openReport } = usePrintService(); @@ -89,15 +90,7 @@ const columns = computed(() => [ { align: 'left', label: t('Employee'), - columnField: { - component: 'userLink', - attrs: ({ row }) => { - return { - workerId: row.workerFk, - name: row.userName, - }; - }, - }, + name: 'workerFk', cardVisible: true, }, { @@ -131,7 +124,6 @@ const columns = computed(() => [ align: 'left', name: 'balance', label: t('Balance'), - format: ({ balance }) => toCurrency(balance), cardVisible: true, }, { @@ -146,12 +138,14 @@ const columns = computed(() => [ actions: [ { title: t('globals.downloadPdf'), + isPrimary: true, icon: 'cloud_download', show: (row) => row.isInvoice, action: (row) => showBalancePdf(row), }, { title: t('Send compensation'), + isPrimary: true, icon: 'outgoing_mail', show: (row) => !!row.isCompensation, action: ({ id }) => @@ -256,6 +250,12 @@ const showBalancePdf = ({ id }) => { <template #column-balance="{ rowIndex }"> {{ toCurrency(balances[rowIndex]?.balance) }} </template> + <template #column-workerFk="{ row }"> + <span class="link" @click.stop> + {{ row.userName }} + <WorkerDescriptorProxy :id="row.workerFk" /> + </span> + </template> <template #column-description="{ row }"> <span class="link" v-if="row.isInvoice" @click.stop> {{ t('bill', { ref: row.description }) }} diff --git a/src/pages/Customer/components/CustomerNewPayment.vue b/src/pages/Customer/components/CustomerNewPayment.vue index ac80fdaa4..fb3804d55 100644 --- a/src/pages/Customer/components/CustomerNewPayment.vue +++ b/src/pages/Customer/components/CustomerNewPayment.vue @@ -3,18 +3,20 @@ import { onBeforeMount, reactive, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import axios from 'axios'; -import { getClientRisk } from '../composables/getClientRisk'; import { useDialogPluginComponent } from 'quasar'; -import FormModelPopup from 'components/FormModelPopup.vue'; + +import { getClientRisk } from '../composables/getClientRisk'; import { usePrintService } from 'composables/usePrintService'; import useNotify from 'src/composables/useNotify.js'; + +import FormModelPopup from 'components/FormModelPopup.vue'; import FetchData from 'components/FetchData.vue'; -import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputNumber from 'components/common/VnInputNumber.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; +import VnAccountNumber from 'src/components/common/VnAccountNumber.vue'; const { t } = useI18n(); const route = useRoute(); @@ -48,7 +50,7 @@ const maxAmount = ref(); const accountingType = ref({}); const isCash = ref(false); const formModelRef = ref(false); - +const amountToReturn = ref(); const filterBanks = { fields: ['id', 'bank', 'accountingTypeFk'], include: { relation: 'accountingType' }, @@ -90,7 +92,7 @@ function setPaymentType(data, accounting) { let descriptions = []; if (accountingType.value.receiptDescription) descriptions.push(accountingType.value.receiptDescription); - if (data.description) descriptions.push(data.description); + if (data.description > 0) descriptions.push(data.description); data.description = descriptions.join(', '); } @@ -100,7 +102,7 @@ const calculateFromAmount = (event) => { }; const calculateFromDeliveredAmount = (event) => { - initialData.amountToReturn = parseFloat(event) - initialData.amountPaid; + amountToReturn.value = event - initialData.amountPaid; }; function onBeforeSave(data) { @@ -121,17 +123,16 @@ async function onDataSaved(formData, { id }) { recipient: formData.email, }); - if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`); + if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`, {}, '_blank'); } finally { if ($props.promise) $props.promise(); if (closeButton.value) closeButton.value.click(); } } -async function accountShortToStandard({ target: { value } }) { +async function getSupplierClientReferences(value) { if (!value) return (initialData.description = ''); - initialData.compensationAccount = value.replace('.', '0'.repeat(11 - value.length)); - const params = { bankAccount: initialData.compensationAccount }; + const params = { bankAccount: value }; const { data } = await axios(`Clients/getClientOrSupplierReference`, { params }); if (!data.clientId) { initialData.description = t('Supplier Compensation Reference', { @@ -241,17 +242,16 @@ async function getAmountPaid() { @update:model-value="getAmountPaid()" /> </VnRow> - - <div v-if="data.bankFk?.accountingType?.code == 'compensation'"> + <div v-if="accountingType.code == 'compensation'"> <div class="text-h6"> {{ t('Compensation') }} </div> <VnRow> - <VnInputNumber + <VnAccountNumber :label="t('Compensation account')" clearable v-model="data.compensationAccount" - @blur="accountShortToStandard" + @blur="getSupplierClientReferences(data.compensationAccount)" /> </VnRow> </div> @@ -261,8 +261,7 @@ async function getAmountPaid() { clearable v-model="data.description" /> - - <div v-if="data.bankFk?.accountingType?.code == 'cash'"> + <div v-if="accountingType.code == 'cash'"> <div class="text-h6">{{ t('Cash') }}</div> <VnRow> <VnInputNumber @@ -274,7 +273,7 @@ async function getAmountPaid() { <VnInputNumber :label="t('Amount to return')" disable - v-model="data.amountToReturn" + v-model="amountToReturn" /> </VnRow> <VnRow> diff --git a/src/pages/Route/Vehicle/Card/VehicleNotes.vue b/src/pages/Route/Vehicle/Card/VehicleNotes.vue new file mode 100644 index 000000000..0afc3c3ed --- /dev/null +++ b/src/pages/Route/Vehicle/Card/VehicleNotes.vue @@ -0,0 +1,35 @@ +<script setup> +import { computed } from 'vue'; +import { useRoute } from 'vue-router'; +import { useState } from 'src/composables/useState'; +import VnNotes from 'src/components/ui/VnNotes.vue'; + +const route = useRoute(); +const state = useState(); +const user = state.getUser(); +const vehicleId = computed(() => route.params.id); + +const noteFilter = computed(() => { + return { + order: 'created DESC', + where: { vehicleFk: vehicleId.value }, + }; +}); + +const body = { + vehicleFk: vehicleId.value, + workerFk: user.value.id, +}; +</script> + +<template> + <VnNotes + url="vehicleObservations" + :add-note="true" + :filter="noteFilter" + :body="body" + style="overflow-y: auto" + required + deletable + /> +</template> diff --git a/src/pages/Travel/ExtraCommunity.vue b/src/pages/Travel/ExtraCommunity.vue index d30629a80..ec898719d 100644 --- a/src/pages/Travel/ExtraCommunity.vue +++ b/src/pages/Travel/ExtraCommunity.vue @@ -18,7 +18,6 @@ import { usePrintService } from 'composables/usePrintService'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import axios from 'axios'; import RightMenu from 'src/components/common/RightMenu.vue'; -import VnPopup from 'src/components/common/VnPopup.vue'; const stateStore = useStateStore(); const { t } = useI18n(); @@ -183,7 +182,6 @@ const columns = computed(() => [ align: 'left', showValue: false, sortable: true, - style: 'min-width: 170px;', }, { label: t('globals.packages'), @@ -507,6 +505,7 @@ watch(route, () => { :props="props" @click="stopEventPropagation($event, col)" :style="col.style" + style="padding-left: 5px" > <component :is="tableColumnComponents[col.name].component" @@ -613,23 +612,10 @@ watch(route, () => { <QTd class="text-right"> <span>{{ entry.volumeKg }}</span> </QTd> - <QTd /> - <QTd /> - <QTd /> - <QTd /> - <QTd> - <QBtn - v-if="entry.evaNotes" - icon="comment" - size="md" - flat - color="primary" - > - <VnPopup - :title="t('globals.observations')" - :content="entry.evaNotes" - /> - </QBtn> + <QTd :colspan="5" class="text-right"> + <span> + {{ entry.evaNotes }} + </span> </QTd> </QTr> </template> @@ -643,7 +629,11 @@ watch(route, () => { } :deep(.q-table) { + table-layout: auto; + width: 100%; border-collapse: collapse; + overflow: hidden; + text-overflow: ellipsis; tbody tr td { &:nth-child(1) { diff --git a/src/router/__tests__/hooks.spec.js b/src/router/__tests__/hooks.spec.js new file mode 100644 index 000000000..97f5eacdc --- /dev/null +++ b/src/router/__tests__/hooks.spec.js @@ -0,0 +1,36 @@ +import { describe, it, expect, vi } from 'vitest'; +import { ref, nextTick } from 'vue'; +import { stateQueryGuard } from 'src/router/hooks'; +import { useStateQueryStore } from 'src/stores/useStateQueryStore'; + +vi.mock('src/stores/useStateQueryStore', () => { + const isLoading = ref(true); + return { + useStateQueryStore: () => ({ + isLoading: () => isLoading, + setLoading: isLoading, + }), + }; +}); + +describe('hooks', () => { + describe('stateQueryGuard', () => { + const foo = { name: 'foo' }; + it('should wait until the state query is not loading and then call next()', async () => { + const next = vi.fn(); + + stateQueryGuard(foo, { name: 'bar' }, next); + expect(next).not.toHaveBeenCalled(); + + useStateQueryStore().setLoading.value = false; + await nextTick(); + expect(next).toHaveBeenCalled(); + }); + + it('should ignore if both routes are the same', () => { + const next = vi.fn(); + stateQueryGuard(foo, foo, next); + expect(next).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/router/hooks.js b/src/router/hooks.js new file mode 100644 index 000000000..bd9e5334f --- /dev/null +++ b/src/router/hooks.js @@ -0,0 +1,95 @@ +import { useRole } from 'src/composables/useRole'; +import { useUserConfig } from 'src/composables/useUserConfig'; +import { useTokenConfig } from 'src/composables/useTokenConfig'; +import { useAcl } from 'src/composables/useAcl'; +import { isLoggedIn } from 'src/utils/session'; +import { useSession } from 'src/composables/useSession'; +import { useStateQueryStore } from 'src/stores/useStateQueryStore'; +import { watch } from 'vue'; +import { i18n } from 'src/boot/i18n'; + +let session = null; +const { t, te } = i18n.global; + +export async function navigationGuard(to, from, next, Router, state) { + if (!session) session = useSession(); + const outLayout = Router.options.routes[0].children.map((r) => r.name); + if (!session.isLoggedIn() && !outLayout.includes(to.name)) { + return next({ name: 'Login', query: { redirect: to.fullPath } }); + } + + if (isLoggedIn()) { + const stateRoles = state.getRoles().value; + if (stateRoles.length === 0) { + await useRole().fetch(); + await useAcl().fetch(); + await useUserConfig().fetch(); + await useTokenConfig().fetch(); + } + const matches = to.matched; + const hasRequiredAcls = matches.every((route) => { + const meta = route.meta; + if (!meta?.acls) return true; + return useAcl().hasAny(meta.acls); + }); + if (!hasRequiredAcls) return next({ path: '/' }); + } + + next(); +} + +export async function stateQueryGuard(to, from, next) { + if (to.name !== from.name) { + const stateQuery = useStateQueryStore(); + await waitUntilFalse(stateQuery.isLoading()); + } + + next(); +} + +export function setPageTitle(to) { + let title = t(`login.title`); + + const matches = to.matched; + if (matches && matches.length > 1) { + const module = matches[1]; + const moduleTitle = module.meta?.title; + if (moduleTitle) { + title = t(`globals.pageTitles.${moduleTitle}`); + } + } + + const childPage = to.meta; + const childPageTitle = childPage?.title; + if (childPageTitle && matches.length > 2) { + if (title != '') title += ': '; + + const moduleLocale = `globals.pageTitles.${childPageTitle}`; + const pageTitle = te(moduleLocale) + ? t(moduleLocale) + : t(`globals.pageTitles.${childPageTitle}`); + const idParam = to.params?.id; + const idPageTitle = `${idParam} - ${pageTitle}`; + const builtTitle = idParam ? idPageTitle : pageTitle; + + title += builtTitle; + } + + document.title = title; +} + +function waitUntilFalse(ref) { + return new Promise((resolve) => { + if (!ref.value) return resolve(); + const stop = watch( + ref, + (val) => { + if (!val) { + stop(); + resolve(); + } + }, + { immediate: true }, + ); + }); +} diff --git a/src/router/index.js b/src/router/index.js index 4403901cb..628a53c8e 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -6,101 +6,25 @@ import { createWebHashHistory, } from 'vue-router'; import routes from './routes'; -import { i18n } from 'src/boot/i18n'; import { useState } from 'src/composables/useState'; -import { useRole } from 'src/composables/useRole'; -import { useUserConfig } from 'src/composables/useUserConfig'; -import { useTokenConfig } from 'src/composables/useTokenConfig'; -import { useAcl } from 'src/composables/useAcl'; -import { isLoggedIn } from 'src/utils/session'; -import { useSession } from 'src/composables/useSession'; +import { navigationGuard, setPageTitle, stateQueryGuard } from './hooks'; -let session = null; -const { t, te } = i18n.global; - -const createHistory = process.env.SERVER - ? createMemoryHistory - : process.env.VUE_ROUTER_MODE === 'history' - ? createWebHistory - : createWebHashHistory; +const webHistory = + process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory; +const createHistory = process.env.SERVER ? createMemoryHistory : webHistory; const Router = createRouter({ scrollBehavior: () => ({ left: 0, top: 0 }), routes, - - // Leave this as is and make changes in quasar.conf.js instead! - // quasar.conf.js -> build -> vueRouterMode - // quasar.conf.js -> build -> publicPath history: createHistory(process.env.VUE_ROUTER_BASE), }); -/* - * If not building with SSR mode, you can - * directly export the Router instantiation; - * - * The function below can be async too; either use - * async/await or return a Promise which resolves - * with the Router instance. - */ export { Router }; -export default defineRouter(function (/* { store, ssrContext } */) { +export default defineRouter(() => { const state = useState(); - Router.beforeEach(async (to, from, next) => { - if (!session) session = useSession(); - const outLayout = Router.options.routes[0].children.map((r) => r.name); - if (!session.isLoggedIn() && !outLayout.includes(to.name)) { - return next({ name: 'Login', query: { redirect: to.fullPath } }); - } - - if (isLoggedIn()) { - const stateRoles = state.getRoles().value; - if (stateRoles.length === 0) { - await useRole().fetch(); - await useAcl().fetch(); - await useUserConfig().fetch(); - await useTokenConfig().fetch(); - } - const matches = to.matched; - const hasRequiredAcls = matches.every((route) => { - const meta = route.meta; - if (!meta?.acls) return true; - return useAcl().hasAny(meta.acls); - }); - if (!hasRequiredAcls) return next({ path: '/' }); - } - - next(); - }); - - Router.afterEach((to) => { - let title = t(`login.title`); - - const matches = to.matched; - if (matches && matches.length > 1) { - const module = matches[1]; - const moduleTitle = module.meta && module.meta.title; - if (moduleTitle) { - title = t(`globals.pageTitles.${moduleTitle}`); - } - } - - const childPage = to.meta; - const childPageTitle = childPage && childPage.title; - if (childPageTitle && matches.length > 2) { - if (title != '') title += ': '; - - const moduleLocale = `globals.pageTitles.${childPageTitle}`; - const pageTitle = te(moduleLocale) - ? t(moduleLocale) - : t(`globals.pageTitles.${childPageTitle}`); - const idParam = to.params && to.params.id; - const idPageTitle = `${idParam} - ${pageTitle}`; - const builtTitle = idParam ? idPageTitle : pageTitle; - - title += builtTitle; - } - document.title = title; - }); + Router.beforeEach((to, from, next) => navigationGuard(to, from, next, Router, state)); + Router.beforeEach(stateQueryGuard); + Router.afterEach(setPageTitle); Router.onError(({ message }) => { const errorMessages = [ diff --git a/src/router/modules/route.js b/src/router/modules/route.js index 0bf3a6eff..0dd41c86e 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -166,7 +166,7 @@ const vehicleCard = { component: () => import('src/pages/Route/Vehicle/Card/VehicleCard.vue'), redirect: { name: 'VehicleSummary' }, meta: { - menu: ['VehicleBasicData'], + menu: ['VehicleBasicData', 'VehicleNotes'], }, children: [ { @@ -187,6 +187,15 @@ const vehicleCard = { }, component: () => import('src/pages/Route/Vehicle/Card/VehicleBasicData.vue'), }, + { + name: 'VehicleNotes', + path: 'notes', + meta: { + title: 'notes', + icon: 'vn:notes', + }, + component: () => import('src/pages/Route/Vehicle/Card/VehicleNotes.vue'), + } ], }; diff --git a/test/cypress/integration/entry/commands.js b/test/cypress/integration/entry/commands.js index 7c96a5440..4d4a8f980 100644 --- a/test/cypress/integration/entry/commands.js +++ b/test/cypress/integration/entry/commands.js @@ -1,6 +1,6 @@ Cypress.Commands.add('selectTravel', (warehouse = '1') => { cy.get('i[data-cy="Travel_icon"]').click(); - cy.get('input[data-cy="Warehouse Out_select"]').type(warehouse); + cy.selectOption('input[data-cy="Warehouse Out_select"]', warehouse); cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); cy.get('button[data-cy="save-filter-travel-form"]').click(); cy.get('tr').eq(1).click(); @@ -9,7 +9,6 @@ Cypress.Commands.add('selectTravel', (warehouse = '1') => { Cypress.Commands.add('deleteEntry', () => { cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click(); cy.waitForElement('div[data-cy="delete-entry"]').click(); - cy.url().should('include', 'list'); }); Cypress.Commands.add('createEntry', () => { diff --git a/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js index 554471008..8185866db 100644 --- a/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js +++ b/test/cypress/integration/entry/entryCard/entryDescriptor.spec.js @@ -28,12 +28,8 @@ describe('EntryDescriptor', () => { cy.get('.q-notification__message') .eq(2) .should('have.text', 'Entry prices recalculated'); - - cy.get('[data-cy="descriptor-more-opts"]').click(); cy.deleteEntry(); - cy.log(previousUrl); - cy.visit(previousUrl); cy.waitForElement('[data-cy="entry-buys"]'); diff --git a/test/cypress/integration/route/vehicle/vehicleNotes.spec.js b/test/cypress/integration/route/vehicle/vehicleNotes.spec.js new file mode 100644 index 000000000..cd92cc4af --- /dev/null +++ b/test/cypress/integration/route/vehicle/vehicleNotes.spec.js @@ -0,0 +1,28 @@ +describe('Vehicle Notes', () => { + const selectors = { + addNoteInput: 'Add note here..._input', + saveNoteBtn: 'saveNote', + deleteNoteBtn: 'notesRemoveNoteBtn', + noteCard: '.column.full-width > :nth-child(1) > .q-card__section--vert', + }; + + const noteText = 'Golpe parachoques trasero'; + const newNoteText = 'probando'; + + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit(`/#/route/vehicle/1/notes`); + }); + + it('Should add new note', () => { + cy.dataCy(selectors.addNoteInput).should('be.visible').type(newNoteText); + cy.dataCy(selectors.saveNoteBtn).click(); + cy.validateContent(selectors.noteCard, newNoteText); + }); + + it('Should delete note', () => { + cy.dataCy(selectors.deleteNoteBtn).first().should('be.visible').click(); + cy.get(selectors.noteCard).first().should('have.text', noteText); + }); +});