From 4cb4ad95326925b0c5174f5ab2809b622dd5f0b2 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Mon, 11 Nov 2024 09:08:55 +0100 Subject: [PATCH] test: refs #7220 #7220 improve more tests --- test/cypress/components/UserPanel.spec.js | 80 ++++++++++- .../components/common/RightMenu.spec.js | 58 +++++++- .../components/common/VnAccountNumber.spec.js | 4 +- .../components/common/VnBreadcrumbs.spec.js | 4 +- test/cypress/components/common/VnCard.spec.js | 89 +++++++++++- .../components/common/VnComponent.spec.js | 107 ++++++++++++++- test/cypress/components/common/VnDms.spec.js | 94 ++++++++++++- .../components/common/VnDmsList.spec.js | 112 ++++++++++++++- .../cypress/components/common/VnInput.spec.js | 99 +++++++++++++- .../components/common/VnInputDate.spec.js | 101 +++++++++++++- .../components/common/VnInputNumber.spec.js | 45 ++++++- .../components/common/VnInputTime.spec.js | 91 ++++++++++++- .../components/common/VnLocation.spec.js | 2 +- .../components/common/VnLogFilter.spec.js | 73 +++++++++- .../cypress/components/common/VnPopup.spec.js | 35 ++++- .../components/common/VnProgressModal.spec.js | 2 +- .../cypress/components/common/VnRadio.spec.js | 42 +++++- .../components/common/VnSectionMain.spec.js | 64 ++++++++- .../components/common/VnSelect.spec.js | 127 +++++++++++++++++- .../components/common/VnSelectCache.spec.js | 72 +++++++++- .../components/common/VnSelectDialog.spec.js | 83 +++++++++++- .../components/common/VnSelectEnum.spec.js | 100 +++++++++++++- .../components/common/VnSmsDialog.spec.js | 2 +- .../components/common/VnSummaryDialog.spec.js | 58 +++++++- .../components/common/VnWeekdayPicker.spec.js | 94 ++++++++++++- .../cypress/components/ui/CardSummary.spec.js | 2 +- test/cypress/components/ui/VnConfirm.spec.js | 69 +++++++++- .../components/ui/VnFilterPanel.spec.js | 92 ++++++++++++- .../components/ui/VnFilterPanelChip.spec.js | 51 ++++++- test/cypress/components/ui/VnImg.spec.js | 82 ++++++++++- .../cypress/components/ui/VnLinkPhone.spec.js | 46 ++++++- test/cypress/components/ui/VnNotes.spec.js | 93 ++++++++++++- test/cypress/components/ui/VnOutForm.spec.js | 57 +++++++- test/cypress/components/ui/VnPaginate.spec.js | 118 +++++++++++++++- test/cypress/components/ui/VnRow.spec.js | 50 ++++++- .../cypress/components/ui/VnSearchbar.spec.js | 87 +++++++++++- test/cypress/components/ui/VnSms.spec.js | 69 +++++++++- .../components/ui/VnSubToolbar.spec.js | 67 ++++++++- test/cypress/components/ui/VnUserLink.spec.js | 50 ++++++- 39 files changed, 2432 insertions(+), 139 deletions(-) diff --git a/test/cypress/components/UserPanel.spec.js b/test/cypress/components/UserPanel.spec.js index b6fa73283..bc18b2568 100644 --- a/test/cypress/components/UserPanel.spec.js +++ b/test/cypress/components/UserPanel.spec.js @@ -1,8 +1,80 @@ -import UserPanel from 'src/components/UserPanel.vue'; +// UserPanel.spec.js +import UserPanel from 'src/components/common/UserPanel.vue'; +import { ref } from 'vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + // Mock composables and dependencies + cy.stub(window, 'useI18n').returns({ + t: (key) => key, + locale: 'en', + }); + cy.stub(window, 'useRouter').returns({}); + cy.stub(window, 'useState').returns({}); + cy.stub(window, 'useSession').returns({}); + cy.stub(window, 'useClipboard').returns({ + copyText: cy.stub().as('copyTextStub'), + }); + cy.stub(window, 'useRole').returns({}); + cy.stub(window, 'useNotify').returns({ + notify: cy.stub().as('notifyStub'), + }); + cy.stub(window, 'axios').as('axiosStub'); + }); + + it('renders user panel components', () => { cy.createWrapper(UserPanel); + cy.getComponent('VnSelect').should('exist'); + cy.getComponent('VnRow').should('exist'); + cy.getComponent('FetchData').should('exist'); + cy.getComponent('VnAvatar').should('exist'); + }); + + it('displays user locale correctly', () => { + cy.createWrapper(UserPanel); + cy.wrap(Cypress.vueWrapper.vm.userLocale).should('equal', 'en'); + }); + + it('updates locale when userLocale is changed', () => { + cy.createWrapper(UserPanel); + cy.wrap(Cypress.vueWrapper.vm.userLocale).set('fr'); + cy.wrap(Cypress.vueWrapper.vm.locale).should('equal', 'fr'); + }); + + it('copies text to clipboard', () => { + cy.createWrapper(UserPanel); + cy.get('.copy-button').click(); + cy.get('@copyTextStub').should('have.been.called'); + }); + + it('notifies user on copy', () => { + cy.createWrapper(UserPanel); + cy.get('.copy-button').click(); + cy.get('@notifyStub').should('have.been.called'); + }); + + it('fetches user data on mount', () => { + cy.createWrapper(UserPanel); + cy.get('@axiosStub').should('have.been.called'); + }); + + it('renders user avatar', () => { + cy.createWrapper(UserPanel); + cy.getComponent('VnAvatar').should('exist'); + }); + + it('handles dark mode toggle', () => { + cy.createWrapper(UserPanel); + cy.get('.dark-mode-toggle').click(); + cy.wrap(Cypress.vueWrapper.vm.$q.dark.isActive).should('be.true'); + }); + + it('handles logout action', () => { + const routerPush = cy.spy().as('routerPush'); + cy.stub(window, 'useRouter').returns({ push: routerPush }); + + cy.createWrapper(UserPanel); + cy.get('.logout-button').click(); + cy.get('@routerPush').should('have.been.calledWith', '/login'); }); }); diff --git a/test/cypress/components/common/RightMenu.spec.js b/test/cypress/components/common/RightMenu.spec.js index 049941ede..81a853a0e 100644 --- a/test/cypress/components/common/RightMenu.spec.js +++ b/test/cypress/components/common/RightMenu.spec.js @@ -1,8 +1,60 @@ +// RightMenu.spec.js import RightMenu from 'src/components/common/RightMenu.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + // Mock stores and composables + cy.stub(window, 'useStateStore').returns({ + rightDrawer: false, + isHeaderMounted: () => true, + }); + cy.stub(window, 'useI18n').returns({ t: (key) => key }); + }); + + it('renders correctly', () => { cy.createWrapper(RightMenu); + cy.get('#actions-append').should('exist'); + }); + + it('observes right panel content changes', () => { + cy.createWrapper(RightMenu); + const rightPanel = document.createElement('div'); + rightPanel.id = 'right-panel'; + document.body.appendChild(rightPanel); + + const newChild = document.createElement('div'); + rightPanel.appendChild(newChild); + + cy.wrap(Cypress.vueWrapper.vm.hasContent).should('equal', 1); + rightPanel.remove(); + }); + + it('hides right drawer when no content and no slot', () => { + cy.createWrapper(RightMenu); + cy.wrap(Cypress.vueWrapper.vm.stateStore.rightDrawer).should('be.false'); + }); + + it('shows right drawer when slot content is provided', () => { + cy.createWrapper(RightMenu, { + slots: { + 'right-panel': '
Slot Content
', + }, + }); + cy.wrap(Cypress.vueWrapper.vm.stateStore.rightDrawer).should('be.true'); + }); + + it('teleports content to #actions-append when header is mounted', () => { + cy.createWrapper(RightMenu); + cy.get('#actions-append').should('exist'); + }); + + it('does not teleport content when header is not mounted', () => { + cy.stub(window, 'useStateStore').returns({ + rightDrawer: false, + isHeaderMounted: () => false, + }); + + cy.createWrapper(RightMenu); + cy.get('#actions-append').should('not.exist'); }); }); diff --git a/test/cypress/components/common/VnAccountNumber.spec.js b/test/cypress/components/common/VnAccountNumber.spec.js index 3421b6026..bda60d07b 100644 --- a/test/cypress/components/common/VnAccountNumber.spec.js +++ b/test/cypress/components/common/VnAccountNumber.spec.js @@ -1,9 +1,7 @@ import VnAccountNumber from 'src/components/common/VnAccountNumber.vue'; import { Quasar, QInput } from 'quasar'; -describe('', () => { - // VNAccountNumber.spec.js - +describe.only('', () => { const mountComponent = (props = {}) => { cy.createWeapper(VnAccountNumber, { global: { diff --git a/test/cypress/components/common/VnBreadcrumbs.spec.js b/test/cypress/components/common/VnBreadcrumbs.spec.js index be1f35f41..ce97ffffa 100644 --- a/test/cypress/components/common/VnBreadcrumbs.spec.js +++ b/test/cypress/components/common/VnBreadcrumbs.spec.js @@ -67,9 +67,9 @@ describe('', () => { router.push('/nometa'); router.isReady().then(() => { - mount(VnBreadcrumbs, { + cy.createWrapper(VnBreadcrumbs, { global: { - plugins: [router, Quasar], + plugins: [router], }, }); cy.get('.breadcrumbs').should('not.exist'); diff --git a/test/cypress/components/common/VnCard.spec.js b/test/cypress/components/common/VnCard.spec.js index 26c131ec3..8c19783ac 100644 --- a/test/cypress/components/common/VnCard.spec.js +++ b/test/cypress/components/common/VnCard.spec.js @@ -1,8 +1,89 @@ import VnCard from 'src/components/common/VnCard.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnCard); +describe.only('', () => { + beforeEach(() => { + // Montar el componente antes de cada prueba con valores predeterminados + cy.mount(VnCard, { + props: { + element: { name: 'Item' }, + id: 1, + isSelected: false, + title: 'Card Title', + showCheckbox: true, + hasInfoIcons: true, + }, + }); + }); + + it('should display title and ID chip', () => { + // Verificar que el título esté presente + cy.contains('Card Title').should('be.visible'); + + // Verificar que la etiqueta ID esté presente y visible + cy.contains('ID: 1').should('be.visible'); + }); + + it('should display checkbox when showCheckbox is true', () => { + // Verificar que el checkbox esté visible cuando showCheckbox es verdadero + cy.get('.q-checkbox').should('exist'); + }); + + it('should emit toggleCardCheck event when checkbox is clicked', () => { + // Espiar el evento `toggleCardCheck` + const onToggleCardCheck = cy.spy().as('toggleCardCheckEvent'); + cy.mount({ + props: { + element: { name: 'Item' }, + showCheckbox: true, + }, + listeners: { + toggleCardCheck: onToggleCardCheck, + }, + }); + + // Hacer clic en el checkbox + cy.get('.q-checkbox').click(); + + // Asegurarse de que el evento `toggleCardCheck` se emitió + cy.get('@toggleCardCheckEvent').should('have.been.calledOnce'); + }); + + it('should display info-icons slot content when hasInfoIcons is true', () => { + // Montar el componente con el slot info-icons + cy.mount({ + props: { + hasInfoIcons: true, + }, + slots: { + 'info-icons': `
Info Icon
`, + }, + }); + + // Verificar que el contenido del slot `info-icons` esté visible + cy.get('.info-icon-slot').should('be.visible'); + }); + + it('should display list-items slot content', () => { + // Montar el componente con el slot `list-items` + cy.mount({ + slots: { + 'list-items': `
List Item
`, + }, + }); + + // Verificar que el contenido del slot `list-items` esté visible + cy.get('.list-item-slot').should('be.visible'); + }); + + it('should display actions slot content', () => { + // Montar el componente con el slot `actions` + cy.mount({ + slots: { + actions: ``, + }, + }); + + // Verificar que el contenido del slot `actions` esté visible + cy.get('.action-button').should('be.visible'); }); }); diff --git a/test/cypress/components/common/VnComponent.spec.js b/test/cypress/components/common/VnComponent.spec.js index 9262615fa..15416e6da 100644 --- a/test/cypress/components/common/VnComponent.spec.js +++ b/test/cypress/components/common/VnComponent.spec.js @@ -1,8 +1,107 @@ +// VnComponent.spec.js import VnComponent from 'src/components/common/VnComponent.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnComponent); +describe.only('', () => { + const basicProp = { + component: 'TestComponent', + attrs: { test: 'value' }, + }; + + const basicComponents = { + TestComponent: { + component: 'CustomTestComponent', + attrs: { customAttr: 'customValue' }, + }, + }; + + it('renders single prop component', () => { + cy.createWrapper(VnComponent, { + props: { + prop: basicProp, + modelValue: 'test', + }, + }); + cy.get('TestComponent').should('exist'); + }); + + it('renders array of prop components', () => { + cy.createWrapper(VnComponent, { + props: { + prop: [basicProp, { ...basicProp, component: 'SecondComponent' }], + modelValue: 'test', + }, + }); + cy.get('TestComponent').should('exist'); + cy.get('SecondComponent').should('exist'); + }); + + it('mixes component attributes correctly', () => { + cy.createWrapper(VnComponent, { + props: { + prop: basicProp, + components: basicComponents, + modelValue: 'test', + }, + }); + + cy.getComponent('CustomTestComponent').invoke('props').should('deep.include', { + test: 'value', + customAttr: 'customValue', + }); + }); + + it('handles event definitions', () => { + const eventProp = { + ...basicProp, + event: 'custom-event', + }; + + const onCustomEvent = cy.spy().as('eventSpy'); + + cy.createWrapper(VnComponent, { + props: { + prop: eventProp, + modelValue: 'test', + onCustomEvent, + }, + }); + + cy.getComponent('TestComponent').trigger('custom-event'); + cy.get('@eventSpy').should('have.been.called'); + }); + + it('applies forced attributes from components', () => { + const componentsWithForced = { + TestComponent: { + ...basicComponents.TestComponent, + forceAttrs: { forcedAttr: 'forced' }, + }, + }; + + cy.createWrapper(VnComponent, { + props: { + prop: basicProp, + components: componentsWithForced, + modelValue: 'test', + }, + }); + + cy.getComponent('CustomTestComponent') + .invoke('props') + .should('deep.include', { forcedAttr: 'forced' }); + }); + + it('handles value bindings', () => { + cy.createWrapper(VnComponent, { + props: { + prop: basicProp, + value: { testValue: 123 }, + modelValue: 'test', + }, + }); + + cy.getComponent('TestComponent') + .invoke('props') + .should('deep.include', { testValue: 123 }); }); }); diff --git a/test/cypress/components/common/VnDms.spec.js b/test/cypress/components/common/VnDms.spec.js index 501a1cb1b..83bb22471 100644 --- a/test/cypress/components/common/VnDms.spec.js +++ b/test/cypress/components/common/VnDms.spec.js @@ -1,8 +1,94 @@ +// VnDms.spec.js import VnDms from 'src/components/common/VnDms.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnDms); +describe.only('', () => { + beforeEach(() => { + cy.stub(window, 'useRoute').returns({}); + cy.stub(window, 'useI18n').returns({ t: (key) => key }); + cy.stub(window, 'axios').as('axiosStub'); + }); + + it('renders with required props', () => { + cy.createWrapper(VnDms, { + props: { + model: 'testModel', + }, + }); + cy.get('.vn-dms').should('exist'); + }); + + it('initializes with default DMS code', () => { + cy.createWrapper(VnDms, { + props: { + model: 'testModel', + defaultDmsCode: 'DMS001', + }, + }); + cy.getComponent('VnSelect').should('have.value', 'DMS001'); + }); + + it('loads form with initial data', () => { + const initialData = { field: 'value' }; + cy.createWrapper(VnDms, { + props: { + model: 'testModel', + formInitialData: initialData, + }, + }); + cy.getComponent('FormModelPopup') + .invoke('props', 'initialData') + .should('deep.equal', initialData); + }); + + it('fetches data from custom URL when provided', () => { + cy.createWrapper(VnDms, { + props: { + model: 'testModel', + url: '/api/custom-dms', + }, + }); + cy.get('@axiosStub').should( + 'have.been.calledWith', + Cypress.sinon.match({ + url: '/api/custom-dms', + }) + ); + }); + + it('emits onDataSaved event when form is submitted', () => { + const onDataSaved = cy.spy().as('savedSpy'); + cy.createWrapper(VnDms, { + props: { + model: 'testModel', + onDataSaved, + }, + }); + + cy.getComponent('FormModelPopup').trigger('saved'); + cy.get('@savedSpy').should('have.been.called'); + }); + + it('renders child components correctly', () => { + cy.createWrapper(VnDms, { + props: { + model: 'testModel', + }, + }); + + cy.getComponent('VnRow').should('exist'); + cy.getComponent('VnSelect').should('exist'); + cy.getComponent('VnInput').should('exist'); + cy.getComponent('FormModelPopup').should('exist'); + }); + + it('handles fetch data errors gracefully', () => { + cy.get('@axiosStub').rejects(new Error('Fetch Error')); + cy.createWrapper(VnDms, { + props: { + model: 'testModel', + }, + }); + // Verify error handling UI elements + cy.get('.error-message').should('exist'); }); }); diff --git a/test/cypress/components/common/VnDmsList.spec.js b/test/cypress/components/common/VnDmsList.spec.js index 1293b7061..6b83ebcec 100644 --- a/test/cypress/components/common/VnDmsList.spec.js +++ b/test/cypress/components/common/VnDmsList.spec.js @@ -1,8 +1,112 @@ +// VnDmsList.spec.js import VnDmsList from 'src/components/common/VnDmsList.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnDmsList); +describe.only('', () => { + beforeEach(() => { + cy.stub(window, 'useRoute').returns({}); + cy.stub(window, 'useQuasar').returns({}); + cy.stub(window, 'useI18n').returns({ t: (key) => key }); + cy.stub(window, 'useSession').returns({ + getTokenMultimedia: () => 'test-token', + }); + cy.stub(window, 'axios').as('axiosStub'); + }); + + it('renders with required model prop', () => { + cy.createWrapper(VnDmsList, { + props: { + model: 'testModel', + }, + }); + cy.get('.vn-dms-list').should('exist'); + }); + + it('displays paginated data', () => { + cy.createWrapper(VnDmsList, { + props: { + model: 'testModel', + }, + }); + cy.getComponent('VnPaginate').should('exist'); + }); + + it('handles file download', () => { + cy.stub(window, 'downloadFile').as('downloadStub'); + + cy.createWrapper(VnDmsList, { + props: { + model: 'testModel', + }, + }); + + cy.get('.download-button').click(); + cy.get('@downloadStub').should('have.been.called'); + }); + + it('shows DMS form dialog', () => { + cy.createWrapper(VnDmsList, { + props: { + model: 'testModel', + }, + }); + + cy.get('.add-dms-button').click(); + cy.getComponent('VnDms').should('exist'); + }); + + it('handles document deletion with confirmation', () => { + cy.createWrapper(VnDmsList, { + props: { + model: 'testModel', + deleteModel: 'deleteTestModel', + }, + }); + + cy.get('.delete-button').click(); + cy.getComponent('VnConfirm').should('exist'); + cy.get('.confirm-delete-button').click(); + cy.get('@axiosStub').should('have.been.called'); + }); + + it('displays user information using VnUserLink', () => { + const testData = { + rows: [{ id: 1, user: { id: 1, name: 'Test User' } }], + }; + + cy.get('@axiosStub').resolves({ data: testData }); + + cy.createWrapper(VnDmsList, { + props: { + model: 'testModel', + }, + }); + + cy.getComponent('VnUserLink').should('exist'); + }); + + it('shows document preview with VnImg', () => { + cy.createWrapper(VnDmsList, { + props: { + model: 'testModel', + }, + }); + + cy.getComponent('VnImg').should('exist'); + }); + + it('allows date filtering', () => { + cy.createWrapper(VnDmsList, { + props: { + model: 'testModel', + }, + }); + + cy.getComponent('VnInputDate').type('2024-03-20'); + cy.get('@axiosStub').should( + 'have.been.calledWith', + Cypress.sinon.match({ + params: { date: '2024-03-20' }, + }) + ); }); }); diff --git a/test/cypress/components/common/VnInput.spec.js b/test/cypress/components/common/VnInput.spec.js index 3b58dde55..8fa46d046 100644 --- a/test/cypress/components/common/VnInput.spec.js +++ b/test/cypress/components/common/VnInput.spec.js @@ -1,8 +1,101 @@ +// VnInput.spec.js import VnInput from 'src/components/common/VnInput.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + cy.stub(window, 'useI18n').returns({ t: (key) => key }); + cy.stub(window, 'useValidator').returns({ + validations: () => ({ + required: (isRequired, value) => (isRequired ? !!value : true), + }), + }); + }); + + it('renders correctly with default props', () => { cy.createWrapper(VnInput); + cy.get('.q-field').should('exist'); + cy.get('input').should('exist'); + }); + + it('binds model value correctly', () => { + const testValue = 'test input'; + cy.createWrapper(VnInput, { + props: { + modelValue: testValue, + }, + }); + cy.get('input').should('have.value', testValue); + }); + + it('emits update:modelValue on input', () => { + const onModelValueUpdate = cy.spy().as('modelUpdateSpy'); + cy.createWrapper(VnInput, { + props: { + 'onUpdate:modelValue': onModelValueUpdate, + }, + }); + cy.get('input').type('new value'); + cy.get('@modelUpdateSpy').should('have.been.calledWith', 'new value'); + }); + + it('applies outlined style when isOutlined is true', () => { + cy.createWrapper(VnInput, { + props: { + isOutlined: true, + }, + }); + cy.get('.q-field--outlined').should('exist'); + }); + + it('displays info text when provided', () => { + cy.createWrapper(VnInput, { + props: { + info: 'Helper text', + }, + }); + cy.get('.q-field__info').should('contain', 'Helper text'); + }); + + it('shows clear button when clearable is true and has value', () => { + cy.createWrapper(VnInput, { + props: { + modelValue: 'test', + clearable: true, + }, + }); + cy.get('.q-field__clear').should('exist'); + }); + + it('clears input when clear button is clicked', () => { + const onModelValueUpdate = cy.spy().as('modelUpdateSpy'); + cy.createWrapper(VnInput, { + props: { + modelValue: 'test', + clearable: true, + 'onUpdate:modelValue': onModelValueUpdate, + }, + }); + cy.get('.q-field__clear').click(); + cy.get('@modelUpdateSpy').should('have.been.calledWith', null); + }); + + it('emits keyup.enter event', () => { + const onEnter = cy.spy().as('enterSpy'); + cy.createWrapper(VnInput, { + props: { + 'onKeyup.enter': onEnter, + }, + }); + cy.get('input').type('{enter}'); + cy.get('@enterSpy').should('have.been.called'); + }); + + it('handles numeric input', () => { + cy.createWrapper(VnInput, { + props: { + modelValue: 123, + }, + }); + cy.get('input').should('have.value', '123'); }); }); diff --git a/test/cypress/components/common/VnInputDate.spec.js b/test/cypress/components/common/VnInputDate.spec.js index c3e128c65..649ac6e0e 100644 --- a/test/cypress/components/common/VnInputDate.spec.js +++ b/test/cypress/components/common/VnInputDate.spec.js @@ -1,8 +1,103 @@ +// VnInputDate.spec.js import VnInputDate from 'src/components/common/VnInputDate.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + cy.stub(window, 'useI18n').returns({ t: (key) => key }); + cy.stub(window, 'useValidator').returns({ + validations: () => ({ + required: (isRequired, value) => (isRequired ? !!value : true), + }), + }); + }); + + it('renders correctly with default props', () => { cy.createWrapper(VnInputDate); + cy.getComponent('VnDate').should('exist'); + cy.get('input').should('exist'); + }); + + it('formats date correctly', () => { + const testDate = '2024-03-20'; + cy.createWrapper(VnInputDate, { + props: { + modelValue: testDate, + }, + }); + cy.get('input').should('have.value', '20/03/2024'); + }); + + it('validates required field', () => { + cy.createWrapper(VnInputDate, { + attrs: { + required: true, + }, + }); + cy.get('input').clear(); + cy.get('.q-field__messages').should('contain', 'Field is required'); + }); + + it('opens date picker popup on click', () => { + cy.createWrapper(VnInputDate); + cy.get('input').click(); + cy.get('.q-date').should('be.visible'); + }); + + it('updates model value when date is selected', () => { + const onModelUpdate = cy.spy().as('modelUpdateSpy'); + cy.createWrapper(VnInputDate, { + props: { + 'onUpdate:modelValue': onModelUpdate, + }, + }); + cy.get('input').click(); + cy.get('.q-date__calendar-item').first().click(); + cy.get('@modelUpdateSpy').should('have.been.called'); + }); + + it('applies outlined style when isOutlined is true', () => { + cy.createWrapper(VnInputDate, { + props: { + isOutlined: true, + }, + }); + cy.get('.q-field--outlined').should('exist'); + }); + + it('handles custom validation rules', () => { + const customRule = (val) => + new Date(val) <= new Date() || 'Date cannot be in the future'; + cy.createWrapper(VnInputDate, { + attrs: { + rules: [customRule], + }, + }); + const futureDate = '31/12/2025'; + cy.get('input').type(futureDate); + cy.get('.q-field__messages').should('contain', 'Date cannot be in the future'); + }); + + it('shows hover state', () => { + cy.createWrapper(VnInputDate); + cy.get('input').trigger('mouseenter'); + cy.get('.q-field--hover').should('exist'); + }); + + it('handles disabled state', () => { + cy.createWrapper(VnInputDate, { + attrs: { + disabled: true, + }, + }); + cy.get('input').should('be.disabled'); + }); + + it('hides event indicator when showEvent is false', () => { + cy.createWrapper(VnInputDate, { + props: { + showEvent: false, + }, + }); + cy.get('.q-field__event-indicator').should('not.exist'); }); }); diff --git a/test/cypress/components/common/VnInputNumber.spec.js b/test/cypress/components/common/VnInputNumber.spec.js index a4035e5b1..845b20d0a 100644 --- a/test/cypress/components/common/VnInputNumber.spec.js +++ b/test/cypress/components/common/VnInputNumber.spec.js @@ -1,8 +1,47 @@ +// VnInputNumber.spec.js import VnInputNumber from 'src/components/common/VnInputNumber.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + it('renders correctly', () => { cy.createWrapper(VnInputNumber); + cy.getComponent('VnInput').should('exist'); + cy.get('input[type="number"]').should('exist'); + }); + + it('accepts numeric input', () => { + cy.createWrapper(VnInputNumber); + cy.get('input').type('123'); + cy.get('input').should('have.value', '123'); + }); + + it('converts string input to number', () => { + const onModelUpdate = cy.spy().as('modelUpdateSpy'); + cy.createWrapper(VnInputNumber, { + props: { + 'onUpdate:modelValue': onModelUpdate, + }, + }); + cy.get('input').type('456'); + cy.get('@modelUpdateSpy').should('have.been.calledWith', 456); + }); + + it('forwards attributes to VnInput', () => { + cy.createWrapper(VnInputNumber, { + attrs: { + placeholder: 'Enter number', + min: '0', + max: '100', + }, + }); + cy.get('input') + .should('have.attr', 'placeholder', 'Enter number') + .and('have.attr', 'min', '0') + .and('have.attr', 'max', '100'); + }); + + it('handles invalid input', () => { + cy.createWrapper(VnInputNumber); + cy.get('input').type('abc'); + cy.get('input').should('have.value', ''); }); }); diff --git a/test/cypress/components/common/VnInputTime.spec.js b/test/cypress/components/common/VnInputTime.spec.js index 25e97ef0f..a965926d4 100644 --- a/test/cypress/components/common/VnInputTime.spec.js +++ b/test/cypress/components/common/VnInputTime.spec.js @@ -1,8 +1,93 @@ +// VnInputTime.spec.js import VnInputTime from 'src/components/common/VnInputTime.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + cy.stub(window, 'useI18n').returns({ t: (key) => key }); + cy.stub(window, 'useValidator').returns({ + validations: () => ({ + required: (isRequired, value) => (isRequired ? !!value : true), + }), + }); + }); + + it('renders correctly with default props', () => { cy.createWrapper(VnInputTime); + cy.getComponent('VnTime').should('exist'); + cy.get('input').should('exist'); + }); + + it('formats time correctly', () => { + const initialTime = '14:30'; + cy.createWrapper(VnInputTime, { + props: { + modelValue: initialTime, + }, + }); + cy.get('input').should('have.value', initialTime); + }); + + it('applies outlined style when isOutlined is true', () => { + cy.createWrapper(VnInputTime, { + props: { + isOutlined: true, + }, + }); + cy.get('.q-field--outlined').should('exist'); + }); + + it('validates required field', () => { + cy.createWrapper(VnInputTime, { + attrs: { + required: true, + }, + }); + cy.get('input').clear(); + cy.get('.q-field__messages').should('contain', 'Field is required'); + }); + + it('opens time picker popup on click', () => { + cy.createWrapper(VnInputTime); + cy.get('input').click(); + cy.get('.q-time').should('be.visible'); + }); + + it('updates model value when time is selected', () => { + const onModelUpdate = cy.spy().as('modelUpdateSpy'); + cy.createWrapper(VnInputTime, { + props: { + 'onUpdate:modelValue': onModelUpdate, + }, + }); + cy.get('input').click(); + cy.get('.q-time__clock--hours .q-time__clock-position').first().click(); + cy.get('@modelUpdateSpy').should('have.been.called'); + }); + + it('shows hover state', () => { + cy.createWrapper(VnInputTime); + cy.get('input').trigger('mouseenter'); + cy.get('.q-field--hover').should('exist'); + }); + + it('applies custom validation rules', () => { + const customRule = (val) => val < '20:00' || 'Time must be before 20:00'; + cy.createWrapper(VnInputTime, { + attrs: { + rules: [customRule], + }, + }); + cy.get('input').type('21:00'); + cy.get('.q-field__messages').should('contain', 'Time must be before 20:00'); + }); + + it('displays time-only format when timeOnly is true', () => { + cy.createWrapper(VnInputTime, { + props: { + timeOnly: true, + }, + }); + cy.get('input').type('14:30'); + cy.get('input').should('have.value', '14:30'); }); }); diff --git a/test/cypress/components/common/VnLocation.spec.js b/test/cypress/components/common/VnLocation.spec.js index 5fb16228d..d884c3d35 100644 --- a/test/cypress/components/common/VnLocation.spec.js +++ b/test/cypress/components/common/VnLocation.spec.js @@ -1,5 +1,5 @@ import VnLocation from 'src/components/common/VnLocation.vue'; -describe.skip('', () => { +describe.only('', () => { const location = { postcode: '46000', city: 'Valencia', diff --git a/test/cypress/components/common/VnLogFilter.spec.js b/test/cypress/components/common/VnLogFilter.spec.js index 288e0a604..538169527 100644 --- a/test/cypress/components/common/VnLogFilter.spec.js +++ b/test/cypress/components/common/VnLogFilter.spec.js @@ -1,8 +1,73 @@ import VnLogFilter from 'src/components/common/VnLogFilter.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnLogFilter); +describe.only('', () => { + const mountComponent = () => { + // Stub del componente FetchData + const FetchDataStub = { + template: '
', + props: ['url', 'filter', 'autoLoad'], + }; + + // Stub del componente VnFilterPanel + const VnFilterPanelStub = { + template: + '
', + props: ['dataKey', 'searchButton'], + }; + + cy.createWrapper(VnLogFilter, { + global: { + components: { + FetchData: FetchDataStub, + VnFilterPanel: VnFilterPanelStub, + }, + }, + props: { + dataKey: 'testDataKey', + }, + }); + }; + + it('renderiza correctamente el componente', () => { + mountComponent(); + cy.get('.vn-filter-panel-stub').should('exist'); + }); + + it('pasa las props correctas a FetchData', () => { + const fetchDataSpy = cy.spy().as('fetchDataSpy'); + const FetchDataStub = { + template: '
', + props: ['url', 'filter', 'autoLoad'], + setup(props) { + fetchDataSpy(props); + }, + }; + + mountComponent({ + global: { + components: { + FetchData: FetchDataStub, + VnFilterPanel: { + template: '
', + props: ['dataKey', 'searchButton'], + }, + }, + }, + props: { + dataKey: 'testDataKey', + }, + }); + + cy.get('@fetchDataSpy').should('have.been.calledWithMatch', { + url: 'Workers/activeWithInheritedRole', + filter: { where: { role: 'salesPerson' } }, + autoLoad: true, + }); + }); + + it('renderiza los slots de VnFilterPanel correctamente', () => { + mountComponent(); + cy.get('.vn-filter-panel-stub').should('exist'); + // Aquí puedes agregar más verificaciones para los contenidos de los slots si es necesario }); }); diff --git a/test/cypress/components/common/VnPopup.spec.js b/test/cypress/components/common/VnPopup.spec.js index 34a88393d..bfa01a3ba 100644 --- a/test/cypress/components/common/VnPopup.spec.js +++ b/test/cypress/components/common/VnPopup.spec.js @@ -1,8 +1,35 @@ import VnPopup from 'src/components/common/VnPopup.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnPopup); +describe('', () => { + const mountComponent = (props = {}, slots = {}) => { + cy.createWrapper(VnPopup, { + props, + slots, + }); + }; + + it('renderiza correctamente el componente', () => { + mountComponent(); + cy.get('.q-popup-proxy').should('exist'); + }); + + it('muestra el título y contenido proporcionados en las props', () => { + const title = 'Título de Prueba'; + const content = 'Contenido de Prueba'; + mountComponent({ title, content }); + cy.get('.header').should('contain', title); + cy.get('.change-detail').should('contain', content); + }); + + it('renderiza los slots de título y contenido personalizados', () => { + mountComponent( + {}, + { + title: '
Título Personalizado
', + content: '
Contenido Personalizado
', + } + ); + cy.get('.custom-title').should('contain', 'Título Personalizado'); + cy.get('.custom-content').should('contain', 'Contenido Personalizado'); }); }); diff --git a/test/cypress/components/common/VnProgressModal.spec.js b/test/cypress/components/common/VnProgressModal.spec.js index 7abda8c53..86b9b9076 100644 --- a/test/cypress/components/common/VnProgressModal.spec.js +++ b/test/cypress/components/common/VnProgressModal.spec.js @@ -1,6 +1,6 @@ import VnProgressModal from 'src/components/common/VnProgressModal.vue'; -describe.skip('', () => { +describe.only('', () => { const mountComponent = (opt) => { cy.createWrapper(VnProgressModal, opt); }; diff --git a/test/cypress/components/common/VnRadio.spec.js b/test/cypress/components/common/VnRadio.spec.js index c22dbf1e3..3b50f009c 100644 --- a/test/cypress/components/common/VnRadio.spec.js +++ b/test/cypress/components/common/VnRadio.spec.js @@ -1,8 +1,42 @@ import VnRadio from 'src/components/common/VnRadio.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnRadio); +describe.only('', () => { + const mountComponent = (options = {}) => { + cy.createWrapper(VnRadio, { + ...options, + }); + }; + + it('renders correctly', () => { + mountComponent(); + cy.get('.q-radio').should('exist'); + }); + + it('applies dark mode correctly', () => { + mountComponent(); + cy.get('.q-radio').should('have.class', '--dark'); + }); + + it('updates model value when clicked', () => { + const modelValue = false; + mountComponent({ modelValue }); + cy.get('.q-radio').click(); + cy.get('.q-radio').should('have.class', 'q-radio--checked'); + }); + + it('passes custom attributes through v-bind', () => { + const customProps = { + color: 'primary', + label: 'Test Label', + }; + mountComponent({ attrs: customProps }); + cy.get('.q-radio') + .should('have.class', 'text-primary') + .and('contain', 'Test Label'); + }); + + it('maintains dense property', () => { + mountComponent(); + cy.get('.q-radio').should('have.class', 'q-radio--dense'); }); }); diff --git a/test/cypress/components/common/VnSectionMain.spec.js b/test/cypress/components/common/VnSectionMain.spec.js index 46e178e2d..3ee51b7c0 100644 --- a/test/cypress/components/common/VnSectionMain.spec.js +++ b/test/cypress/components/common/VnSectionMain.spec.js @@ -1,12 +1,70 @@ +// VnSectionMain.spec.js import VnSectionMain from 'src/components/common/VnSectionMain.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + // Mock stores and composables + cy.stub(window, 'useStateStore').returns({ + leftDrawer: false, + }); + + cy.stub(window, 'useQuasar').returns({ + screen: { + gt: { + xs: true, + }, + }, + }); + }); + + it('renders main layout components', () => { + cy.createWrapper(VnSectionMain); + cy.get('.q-drawer').should('exist'); + cy.get('.q-page-container').should('exist'); + cy.get('.q-page').should('exist'); + }); + + it('initializes left drawer based on screen size and prop', () => { cy.createWrapper(VnSectionMain, { props: { leftDrawer: true, }, }); + + cy.wrap(Cypress.vueWrapper.vm.stateStore.leftDrawer).should('be.true'); + }); + + it('hides drawer on small screens regardless of prop', () => { + cy.stub(window, 'useQuasar').returns({ + screen: { + gt: { + xs: false, + }, + }, + }); + + cy.createWrapper(VnSectionMain, { + props: { + leftDrawer: true, + }, + }); + + cy.wrap(Cypress.vueWrapper.vm.stateStore.leftDrawer).should('be.false'); + }); + + it('renders LeftMenu inside drawer', () => { + cy.createWrapper(VnSectionMain); + cy.getComponent('LeftMenu').should('exist'); + }); + + it('renders RouterView for main content', () => { + cy.createWrapper(VnSectionMain); + cy.get('.q-page').should('exist'); + cy.getComponent('RouterView').should('exist'); + }); + + it('applies correct drawer width', () => { + cy.createWrapper(VnSectionMain); + cy.get('.q-drawer').should('have.attr', 'width', '256'); }); }); diff --git a/test/cypress/components/common/VnSelect.spec.js b/test/cypress/components/common/VnSelect.spec.js index 6488728d0..b6f50be02 100644 --- a/test/cypress/components/common/VnSelect.spec.js +++ b/test/cypress/components/common/VnSelect.spec.js @@ -1,8 +1,129 @@ +// VnSelect.spec.js import VnSelect from 'src/components/common/VnSelect.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + cy.stub(window, 'useI18n').returns({ t: (key) => key }); + cy.stub(window, 'useValidator').returns({ + validations: () => ({ + required: (isRequired, value) => (isRequired ? !!value : true), + }), + }); + }); + + const defaultOptions = [ + { id: 1, name: 'Option 1' }, + { id: 2, name: 'Option 2' }, + ]; + + it('renders correctly with default props', () => { cy.createWrapper(VnSelect); + cy.get('.q-select').should('exist'); + }); + + it('displays options with default option label and value', () => { + cy.createWrapper(VnSelect, { + props: { + options: defaultOptions, + }, + }); + cy.get('.q-select').click(); + cy.get('.q-item').should('have.length', 2); + cy.get('.q-item').first().should('contain', 'Option 1'); + }); + + it('uses custom option label and value', () => { + const customOptions = [ + { code: 'A', title: 'Title A' }, + { code: 'B', title: 'Title B' }, + ]; + + cy.createWrapper(VnSelect, { + props: { + options: customOptions, + optionLabel: 'title', + optionValue: 'code', + }, + }); + + cy.get('.q-select').click(); + cy.get('.q-item').first().should('contain', 'Title A'); + }); + + it('emits update:modelValue when option is selected', () => { + const onModelUpdate = cy.spy().as('modelUpdateSpy'); + cy.createWrapper(VnSelect, { + props: { + options: defaultOptions, + 'onUpdate:modelValue': onModelUpdate, + }, + }); + + cy.get('.q-select').click(); + cy.get('.q-item').first().click(); + cy.get('@modelUpdateSpy').should('have.been.calledWith', 1); + }); + + it('filters options based on optionFilter and optionFilterValue', () => { + const filteredOptions = [ + { id: 1, name: 'Option 1', type: 'A' }, + { id: 2, name: 'Option 2', type: 'B' }, + ]; + + cy.createWrapper(VnSelect, { + props: { + options: filteredOptions, + optionFilter: 'type', + optionFilterValue: 'A', + }, + }); + + cy.get('.q-select').click(); + cy.get('.q-item').should('have.length', 1); + cy.get('.q-item').should('contain', 'Option 1'); + }); + + it('loads options from URL when provided', () => { + cy.createWrapper(VnSelect, { + props: { + url: '/api/options', + }, + }); + + cy.getComponent('FetchData').should('exist'); + cy.get('@axiosStub').should( + 'have.been.calledWith', + Cypress.sinon.match({ + url: '/api/options', + }) + ); + }); + + it('emits remove event when clear button is clicked', () => { + const onRemove = cy.spy().as('removeSpy'); + cy.createWrapper(VnSelect, { + props: { + modelValue: 1, + options: defaultOptions, + onRemove, + }, + }); + + cy.get('.q-field__clear').click(); + cy.get('@removeSpy').should('have.been.called'); + }); + + it('updates options externally', () => { + cy.createWrapper(VnSelect, { + props: { + options: defaultOptions, + }, + }); + + const newOptions = [{ id: 3, name: 'Option 3' }]; + cy.wrap(Cypress.vueWrapper).invoke('setProps', { options: newOptions }); + cy.get('.q-select').click(); + cy.get('.q-item').should('have.length', 1); + cy.get('.q-item').should('contain', 'Option 3'); }); }); diff --git a/test/cypress/components/common/VnSelectCache.spec.js b/test/cypress/components/common/VnSelectCache.spec.js index c10514879..3ee51b7c0 100644 --- a/test/cypress/components/common/VnSelectCache.spec.js +++ b/test/cypress/components/common/VnSelectCache.spec.js @@ -1,8 +1,70 @@ -import VnSelectCache from 'src/components/common/VnSelectCache.vue'; +// VnSectionMain.spec.js +import VnSectionMain from 'src/components/common/VnSectionMain.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnSelectCache); +describe.only('', () => { + beforeEach(() => { + // Mock stores and composables + cy.stub(window, 'useStateStore').returns({ + leftDrawer: false, + }); + + cy.stub(window, 'useQuasar').returns({ + screen: { + gt: { + xs: true, + }, + }, + }); + }); + + it('renders main layout components', () => { + cy.createWrapper(VnSectionMain); + cy.get('.q-drawer').should('exist'); + cy.get('.q-page-container').should('exist'); + cy.get('.q-page').should('exist'); + }); + + it('initializes left drawer based on screen size and prop', () => { + cy.createWrapper(VnSectionMain, { + props: { + leftDrawer: true, + }, + }); + + cy.wrap(Cypress.vueWrapper.vm.stateStore.leftDrawer).should('be.true'); + }); + + it('hides drawer on small screens regardless of prop', () => { + cy.stub(window, 'useQuasar').returns({ + screen: { + gt: { + xs: false, + }, + }, + }); + + cy.createWrapper(VnSectionMain, { + props: { + leftDrawer: true, + }, + }); + + cy.wrap(Cypress.vueWrapper.vm.stateStore.leftDrawer).should('be.false'); + }); + + it('renders LeftMenu inside drawer', () => { + cy.createWrapper(VnSectionMain); + cy.getComponent('LeftMenu').should('exist'); + }); + + it('renders RouterView for main content', () => { + cy.createWrapper(VnSectionMain); + cy.get('.q-page').should('exist'); + cy.getComponent('RouterView').should('exist'); + }); + + it('applies correct drawer width', () => { + cy.createWrapper(VnSectionMain); + cy.get('.q-drawer').should('have.attr', 'width', '256'); }); }); diff --git a/test/cypress/components/common/VnSelectDialog.spec.js b/test/cypress/components/common/VnSelectDialog.spec.js index 73966aef2..188703358 100644 --- a/test/cypress/components/common/VnSelectDialog.spec.js +++ b/test/cypress/components/common/VnSelectDialog.spec.js @@ -1,8 +1,85 @@ +// VnSelectDialog.spec.js import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + // Mock role and ACL composables + cy.stub(window, 'useRole').returns({ + hasAny: cy.stub().as('roleHasAnySpy'), + }); + + cy.stub(window, 'useAcl').returns({ + hasAny: cy.stub().as('aclHasAnySpy'), + }); + }); + + it('renders select component', () => { cy.createWrapper(VnSelectDialog); + cy.getComponent('VnSelect').should('exist'); + }); + + it('allows creation for developer role by default', () => { + cy.get('@roleHasAnySpy').returns(true); + + cy.createWrapper(VnSelectDialog); + cy.get('.create-action-button').should('exist'); + }); + + it('hides creation button when user lacks role permission', () => { + cy.get('@roleHasAnySpy').returns(false); + + cy.createWrapper(VnSelectDialog); + cy.get('.create-action-button').should('not.exist'); + }); + + it('checks ACL permissions when provided', () => { + cy.get('@aclHasAnySpy').returns(true); + + cy.createWrapper(VnSelectDialog, { + props: { + acls: ['create:item'], + }, + }); + + cy.get('.create-action-button').should('exist'); + cy.get('@aclHasAnySpy').should('have.been.calledWith', ['create:item']); + }); + + it('displays custom action icon', () => { + cy.get('@roleHasAnySpy').returns(true); + + cy.createWrapper(VnSelectDialog, { + props: { + actionIcon: 'edit', + }, + }); + + cy.get('.create-action-button .q-icon').should('have.attr', 'name', 'edit'); + }); + + it('shows tooltip when provided', () => { + cy.get('@roleHasAnySpy').returns(true); + + cy.createWrapper(VnSelectDialog, { + props: { + tooltip: 'Add new item', + }, + }); + + cy.get('.create-action-button').trigger('mouseenter'); + cy.get('.q-tooltip').should('contain', 'Add new item'); + }); + + it('emits model update when value changes', () => { + const onModelUpdate = cy.spy().as('modelUpdateSpy'); + + cy.createWrapper(VnSelectDialog, { + props: { + 'onUpdate:modelValue': onModelUpdate, + }, + }); + + cy.getComponent('VnSelect').trigger('update:modelValue', 'newValue'); + cy.get('@modelUpdateSpy').should('have.been.calledWith', 'newValue'); }); }); diff --git a/test/cypress/components/common/VnSelectEnum.spec.js b/test/cypress/components/common/VnSelectEnum.spec.js index 119e35d80..e1f3bd39f 100644 --- a/test/cypress/components/common/VnSelectEnum.spec.js +++ b/test/cypress/components/common/VnSelectEnum.spec.js @@ -1,8 +1,100 @@ +// VnSelectEnum.spec.js import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnSelectEnum); +describe.only('', () => { + beforeEach(() => { + cy.stub(window, 'axios').as('axiosStub'); + }); + + it('renders without errors', () => { + cy.createWrapper(VnSelectEnum, { + props: { + table: 'test_table', + column: 'test_column', + }, + }); + cy.getComponent('VnSelect').should('exist'); + }); + + it('loads enum options from API on mount', () => { + const mockEnumData = ['OPTION1', 'OPTION2']; + cy.get('@axiosStub').resolves({ data: mockEnumData }); + + cy.createWrapper(VnSelectEnum, { + props: { + table: 'test_table', + column: 'test_column', + }, + }); + + cy.get('@axiosStub').should('have.been.calledWith', { + method: 'get', + url: '/api/enums/vn/test_table/test_column', + }); + }); + + it('uses custom schema when provided', () => { + cy.createWrapper(VnSelectEnum, { + props: { + schema: 'custom', + table: 'test_table', + column: 'test_column', + }, + }); + + cy.get('@axiosStub').should('have.been.calledWith', { + method: 'get', + url: '/api/enums/custom/test_table/test_column', + }); + }); + + it('uses translation function when provided', () => { + const translation = (value) => `Translated ${value}`; + const mockEnumData = ['OPTION1']; + cy.get('@axiosStub').resolves({ data: mockEnumData }); + + cy.createWrapper(VnSelectEnum, { + props: { + table: 'test_table', + column: 'test_column', + translation, + }, + }); + + cy.getComponent('VnSelect') + .invoke('props', 'options') + .should('deep.equal', [{ label: 'Translated OPTION1', value: 'OPTION1' }]); + }); + + it('uses default options when provided', () => { + const defaultOptions = [ + { label: 'Default 1', value: 'DEFAULT1' }, + { label: 'Default 2', value: 'DEFAULT2' }, + ]; + + cy.createWrapper(VnSelectEnum, { + props: { + table: 'test_table', + column: 'test_column', + defaultOptions, + }, + }); + + cy.getComponent('VnSelect') + .invoke('props', 'options') + .should('deep.equal', defaultOptions); + }); + + it('handles API error gracefully', () => { + cy.get('@axiosStub').rejects(new Error('API Error')); + + cy.createWrapper(VnSelectEnum, { + props: { + table: 'test_table', + column: 'test_column', + }, + }); + + cy.getComponent('VnSelect').invoke('props', 'options').should('deep.equal', []); }); }); diff --git a/test/cypress/components/common/VnSmsDialog.spec.js b/test/cypress/components/common/VnSmsDialog.spec.js index 07ef7b0f8..c7f2a8c6e 100644 --- a/test/cypress/components/common/VnSmsDialog.spec.js +++ b/test/cypress/components/common/VnSmsDialog.spec.js @@ -2,7 +2,7 @@ /* eslint-disable cypress/no-assigning-return-values */ import VnSmsDialog from 'src/components/common/VnSmsDialog.vue'; -describe.skip('', () => { +describe.only('', () => { const defaultProps = { phone: '123456789', subject: 'Test Subject', diff --git a/test/cypress/components/common/VnSummaryDialog.spec.js b/test/cypress/components/common/VnSummaryDialog.spec.js index 5896631fb..762b68971 100644 --- a/test/cypress/components/common/VnSummaryDialog.spec.js +++ b/test/cypress/components/common/VnSummaryDialog.spec.js @@ -1,8 +1,58 @@ +// VnSummaryDialog.spec.js import VnSummaryDialog from 'src/components/common/VnSummaryDialog.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnSummaryDialog); +describe.only('', () => { + const TestSummaryComponent = { + template: '
Test Summary Content
', + props: ['id'], + }; + + beforeEach(() => { + cy.stub(window, 'useDialogPluginComponent').returns({ + dialogRef: cy.stub(), + onDialogHide: cy.stub().as('onDialogHideStub'), + }); + }); + + it('renders correctly with required props', () => { + cy.createWrapper(VnSummaryDialog, { + props: { + id: 1, + summary: TestSummaryComponent, + }, + }); + cy.get('.q-dialog').should('exist'); + }); + + it('renders dynamic summary component with correct props', () => { + cy.createWrapper(VnSummaryDialog, { + props: { + id: 123, + summary: TestSummaryComponent, + }, + }); + cy.get('.test-summary').should('exist'); + cy.get('.test-summary').invoke('prop', 'id').should('equal', 123); + }); + + it('handles dialog hide event', () => { + cy.createWrapper(VnSummaryDialog, { + props: { + id: 1, + summary: TestSummaryComponent, + }, + }); + cy.get('.q-dialog').trigger('hide'); + cy.get('@onDialogHideStub').should('have.been.called'); + }); + + it('applies full-width class to dialog', () => { + cy.createWrapper(VnSummaryDialog, { + props: { + id: 1, + summary: TestSummaryComponent, + }, + }); + cy.get('.q-dialog').should('have.class', 'full-width'); }); }); diff --git a/test/cypress/components/common/VnWeekdayPicker.spec.js b/test/cypress/components/common/VnWeekdayPicker.spec.js index 46363e8e8..ff4cf57b5 100644 --- a/test/cypress/components/common/VnWeekdayPicker.spec.js +++ b/test/cypress/components/common/VnWeekdayPicker.spec.js @@ -1,8 +1,94 @@ +// VnWeekdayPicker.spec.js import VnWeekdayPicker from 'src/components/common/VnWeekdayPicker.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnWeekdayPicker); +describe.only('', () => { + beforeEach(() => { + // Mock weekday store + cy.stub(window, 'useWeekdayStore').returns({ + getLocalesMap: [ + { index: 0, localeChar: 'M' }, + { index: 1, localeChar: 'T' }, + { index: 2, localeChar: 'W' }, + { index: 3, localeChar: 'T' }, + { index: 4, localeChar: 'F' }, + { index: 5, localeChar: 'S' }, + { index: 6, localeChar: 'S' }, + ], + }); + }); + + it('renders all weekday buttons', () => { + cy.createWrapper(VnWeekdayPicker, { + props: { + wdays: [false, false, false, false, false, false, false], + }, + }); + cy.get('.q-btn').should('have.length', 7); + }); + + it('displays correct weekday labels', () => { + cy.createWrapper(VnWeekdayPicker, { + props: { + wdays: [false, false, false, false, false, false, false], + }, + }); + cy.get('.q-btn').first().should('contain', 'M'); + cy.get('.q-btn').last().should('contain', 'S'); + }); + + it('toggles weekday selection on click', () => { + const onUpdateWdays = cy.spy().as('updateSpy'); + cy.createWrapper(VnWeekdayPicker, { + props: { + wdays: [false, false, false, false, false, false, false], + 'onUpdate:wdays': onUpdateWdays, + }, + }); + + cy.get('.q-btn').first().click(); + cy.get('@updateSpy').should('have.been.calledWith', [ + true, + false, + false, + false, + false, + false, + false, + ]); + }); + + it('applies primary color to selected weekdays', () => { + cy.createWrapper(VnWeekdayPicker, { + props: { + wdays: [true, false, true, false, false, false, false], + }, + }); + + cy.get('.q-btn').eq(0).should('have.class', 'bg-primary'); + cy.get('.q-btn').eq(1).should('not.have.class', 'bg-primary'); + cy.get('.q-btn').eq(2).should('have.class', 'bg-primary'); + }); + + it('maintains selection state after multiple toggles', () => { + const onUpdateWdays = cy.spy().as('updateSpy'); + cy.createWrapper(VnWeekdayPicker, { + props: { + wdays: [false, false, false, false, false, false, false], + 'onUpdate:wdays': onUpdateWdays, + }, + }); + + cy.get('.q-btn').first().click(); + cy.get('.q-btn').first().click(); + + cy.get('@updateSpy').should('have.been.calledWith', [ + false, + false, + false, + false, + false, + false, + false, + ]); }); }); diff --git a/test/cypress/components/ui/CardSummary.spec.js b/test/cypress/components/ui/CardSummary.spec.js index 8c466aded..e6e15ccff 100644 --- a/test/cypress/components/ui/CardSummary.spec.js +++ b/test/cypress/components/ui/CardSummary.spec.js @@ -1,6 +1,6 @@ import CardSummary from 'src/components/ui/CardSummary.vue'; import { createRouter, createWebHistory } from 'vue-router'; -describe.skip('', () => { +describe.only('', () => { let router, wrapper; beforeEach(() => { diff --git a/test/cypress/components/ui/VnConfirm.spec.js b/test/cypress/components/ui/VnConfirm.spec.js index 111770197..7536ef364 100644 --- a/test/cypress/components/ui/VnConfirm.spec.js +++ b/test/cypress/components/ui/VnConfirm.spec.js @@ -1,8 +1,71 @@ import VnConfirm from 'src/components/ui/VnConfirm.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + it('renders with default props', () => { cy.createWrapper(VnConfirm); + cy.get('.q-dialog').should('exist'); + }); + + it('renders with custom icon and title', () => { + cy.createWrapper(VnConfirm, { + props: { + icon: 'warning', + title: 'Test Title', + }, + }); + cy.get('.q-icon').should('contain', 'warning'); + cy.get('.dialog-title').should('contain', 'Test Title'); + }); + + it('displays custom message', () => { + cy.createWrapper(VnConfirm, { + props: { + message: 'Test Message', + }, + }); + cy.get('.dialog-content').should('contain', 'Test Message'); + }); + + it('emits confirm event when confirmed', () => { + const onConfirm = cy.spy().as('confirmSpy'); + cy.createWrapper(VnConfirm, { + props: { + onConfirm: onConfirm, + }, + }); + cy.get('.confirm-button').click(); + cy.get('@confirmSpy').should('have.been.called'); + }); + + it('handles promise execution', () => { + const testPromise = cy.stub().resolves('success'); + cy.createWrapper(VnConfirm, { + props: { + promise: testPromise, + data: { testKey: 'testValue' }, + }, + }); + cy.get('.confirm-button').click(); + cy.wrap(testPromise).should('have.been.calledWith', { testKey: 'testValue' }); + }); + + it('shows and hides dialog via exposed methods', () => { + cy.createWrapper(VnConfirm).then((wrapper) => { + wrapper.show(); + cy.get('.q-dialog').should('be.visible'); + wrapper.hide(); + cy.get('.q-dialog').should('not.be.visible'); + }); + }); + + it('handles dialog plugin events', () => { + const onHide = cy.spy().as('hideSpy'); + cy.createWrapper(VnConfirm, { + props: { + onHide: onHide, + }, + }); + cy.get('.cancel-button').click(); + cy.get('@hideSpy').should('have.been.called'); }); }); diff --git a/test/cypress/components/ui/VnFilterPanel.spec.js b/test/cypress/components/ui/VnFilterPanel.spec.js index ea5f5aaa2..d86255517 100644 --- a/test/cypress/components/ui/VnFilterPanel.spec.js +++ b/test/cypress/components/ui/VnFilterPanel.spec.js @@ -1,8 +1,90 @@ -import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +// VnFilterPanel.spec.js +import VnFilterPanel from 'src/components/common/VnFilterPanel.vue'; +import { toDate } from 'src/filters'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnFilterPanel); +describe.only('', () => { + beforeEach(() => { + cy.stub(window, 'useRoute').returns({ + query: {}, + }); + }); + + const mountComponent = (props = {}) => { + cy.createWrapper(VnFilterPanel, { + props: { + dataKey: 'test-key', + ...props, + }, + }); + }; + + it('renders correctly with required props', () => { + mountComponent(); + cy.get('.vn-filter-panel').should('exist'); + }); + + it('handles modelValue changes', () => { + const modelValue = { search: 'test' }; + mountComponent({ modelValue }); + cy.get('.filter-chip').should('exist'); + cy.get('.filter-chip').should('contain', 'test'); + }); + + it('shows search button when searchButton prop is true', () => { + mountComponent({ searchButton: true }); + cy.get('.search-button').should('exist'); + }); + + it('hides search button when searchButton prop is false', () => { + mountComponent({ searchButton: false }); + cy.get('.search-button').should('not.exist'); + }); + + it('prevents removal of unremovable params', () => { + const modelValue = { fixed: 'value', removable: 'test' }; + mountComponent({ + modelValue, + unremovableParams: ['fixed'], + }); + + cy.get('.filter-chip[data-param="fixed"] .remove-button').should('not.exist'); + cy.get('.filter-chip[data-param="removable"] .remove-button').should('exist'); + }); + + it('emits update:modelValue when filter is removed', () => { + const modelValue = { param: 'value' }; + const onUpdateModelValue = cy.spy().as('updateSpy'); + + mountComponent({ + modelValue, + 'onUpdate:modelValue': onUpdateModelValue, + }); + + cy.get('.filter-chip .remove-button').click(); + cy.get('@updateSpy').should('have.been.calledWith', {}); + }); + + it('shows all filters when showAll is true', () => { + const modelValue = { param1: 'value1', param2: 'value2' }; + mountComponent({ + modelValue, + showAll: true, + }); + cy.get('.filter-chip').should('have.length', 2); + }); + + it('formats date values correctly', () => { + const modelValue = { date: '2024-03-20' }; + mountComponent({ modelValue }); + cy.get('.filter-chip').should('contain', toDate('2024-03-20')); + }); + + it('syncs with route query params', () => { + cy.stub(window, 'useRoute').returns({ + query: { search: 'queryTest' }, + }); + + mountComponent(); + cy.get('.filter-chip').should('contain', 'queryTest'); }); }); diff --git a/test/cypress/components/ui/VnFilterPanelChip.spec.js b/test/cypress/components/ui/VnFilterPanelChip.spec.js index fa8979f77..0a5e3173f 100644 --- a/test/cypress/components/ui/VnFilterPanelChip.spec.js +++ b/test/cypress/components/ui/VnFilterPanelChip.spec.js @@ -1,8 +1,49 @@ -import VnFilterPanelChip from 'src/components/ui/VnFilterPanelChip.vue'; +// VnFilterPanelChip.spec.js +import VnFilterPanelChip from 'src/components/common/VnFilterPanelChip.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnFilterPanelChip); +describe.only('', () => { + const mountComponent = (props = {}, slots = {}) => { + cy.createWrapper(VnFilterPanelChip, { + props, + slots, + }); + }; + + it('renders correctly with default props', () => { + mountComponent(); + cy.get('.q-chip').should('exist'); + }); + + it('applies default classes and attributes', () => { + mountComponent(); + cy.get('.q-chip') + .should('have.class', 'text-dark') + .and('have.class', 'bg-primary') + .and('have.attr', 'size', 'sm'); + }); + + it('displays the label icon', () => { + mountComponent(); + cy.get('.q-chip .q-icon').should('contain', 'label'); + }); + + it('renders slot content correctly', () => { + mountComponent( + {}, + { + default: 'Test Content', + } + ); + cy.get('.q-chip').should('contain', 'Test Content'); + }); + + it('forwards attributes via v-bind', () => { + mountComponent({ + class: 'custom-class', + 'data-test': 'test-value', + }); + cy.get('.q-chip') + .should('have.class', 'custom-class') + .and('have.attr', 'data-test', 'test-value'); }); }); diff --git a/test/cypress/components/ui/VnImg.spec.js b/test/cypress/components/ui/VnImg.spec.js index e0efefa84..a1afbc84a 100644 --- a/test/cypress/components/ui/VnImg.spec.js +++ b/test/cypress/components/ui/VnImg.spec.js @@ -1,8 +1,82 @@ import VnImg from 'src/components/ui/VnImg.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnImg); +describe.only('', () => { + beforeEach(() => { + // Mock useSession composable + cy.stub(window, 'useSession').returns({ + getTokenMultimedia: () => 'mock-token', + }); + }); + const mountComponent = (props = {}) => { + cy.createWrapper(VnImg, { + props, + }); + }; + + it('renders with default props', () => { + mountComponent({ id: 1 }); + cy.get('img') + .should('have.attr', 'src') + .and('include', 'Images') + .and('include', 'catalog') + .and('include', '200x200'); + }); + + it('renders with custom storage and collection', () => { + mountComponent({ + props: { + id: 1, + storage: 'CustomStorage', + collection: 'custom-collection', + resolution: '300x300', + }, + }); + cy.get('img') + .should('have.attr', 'src') + .and('include', 'CustomStorage') + .and('include', 'custom-collection') + .and('include', '300x300'); + }); + + it('shows zoom functionality when enabled', () => { + mountComponent({ + props: { + id: 1, + zoom: true, + zoomResolution: '800x800', + }, + }); + cy.get('img').click(); + cy.get('.zoomed-image').should('exist'); + }); + + it('disables zoom functionality when configured', () => { + mountComponent({ + props: { + id: 1, + zoom: false, + }, + }); + cy.get('img').click(); + cy.get('.zoomed-image').should('not.exist'); + }); + + it('includes authentication token in image URL', () => { + mountComponent({ + props: { + id: 1, + }, + }); + cy.get('img').should('have.attr', 'src').and('include', 'mock-token'); + }); + + it('shows fallback image when loading fails', () => { + mountComponent({ + props: { + id: 1, + }, + }); + cy.get('img').trigger('error'); + cy.get('img').should('have.attr', 'src', '/no-user.png'); }); }); diff --git a/test/cypress/components/ui/VnLinkPhone.spec.js b/test/cypress/components/ui/VnLinkPhone.spec.js index 478ada35b..915630c7f 100644 --- a/test/cypress/components/ui/VnLinkPhone.spec.js +++ b/test/cypress/components/ui/VnLinkPhone.spec.js @@ -1,8 +1,46 @@ import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnLinkPhone); +describe.only('', () => { + const mountComponent = (props = {}) => { + cy.createWrapper(VnLinkPhone, { + props, + }); + }; + + it('should not render button when phone number is null', () => { + mountComponent(); + cy.get('.q-btn').should('not.exist'); + }); + + it('should render button when phone number is provided', () => { + mountComponent({ phoneNumber: '1234567890' }); + cy.get('.q-btn').should('exist'); + }); + + it('should generate correct SIP link from phone number', () => { + mountComponent({ phoneNumber: '1234567890' }); + cy.get('.q-btn').should('have.attr', 'href', 'sip:1234567890'); + }); + + it('should have correct button attributes', () => { + mountComponent({ phoneNumber: '1234567890' }); + cy.get('.q-btn') + .should('have.class', 'q-btn--flat') + .should('have.class', 'q-btn--round') + .should('have.class', 'text-primary') + .should('have.attr', 'padding', 'none'); + }); + + it('should display phone icon', () => { + mountComponent({ phoneNumber: '1234567890' }); + cy.get('.q-btn .q-icon').should('contain', 'phone'); + }); + + it('should stop click event propagation', () => { + const onClick = cy.spy().as('clickSpy'); + mountComponent({ phoneNumber: '1234567890' }); + cy.get('.q-btn').parent().on('click', onClick); + cy.get('.q-btn').click(); + cy.get('@clickSpy').should('not.have.been.called'); }); }); diff --git a/test/cypress/components/ui/VnNotes.spec.js b/test/cypress/components/ui/VnNotes.spec.js index 7e3768997..d7c9e0aae 100644 --- a/test/cypress/components/ui/VnNotes.spec.js +++ b/test/cypress/components/ui/VnNotes.spec.js @@ -1,8 +1,93 @@ -import VnNotes from 'src/components/ui/VnNotes.vue'; +// VnNotes.spec.js +import VnNotes from 'src/components/common/VnNotes.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + // Mock axios to prevent real HTTP requests + cy.stub(window, 'axios').as('axiosStub'); + }); + + it('renders without errors', () => { cy.createWrapper(VnNotes); + cy.get('.vn-notes').should('exist'); + }); + + it('loads notes data correctly on mount', () => { + cy.createWrapper(VnNotes); + cy.get('@axiosStub').should('have.been.called'); + }); + + it('displays the add note section when addNote prop is true', () => { + cy.createWrapper(VnNotes, { + props: { + addNote: true, + }, + }); + cy.get('.add-note-section').should('be.visible'); + }); + + it('hides the add note section when addNote prop is false', () => { + cy.createWrapper(VnNotes, { + props: { + addNote: false, + }, + }); + cy.get('.add-note-section').should('not.exist'); + }); + + it('allows the user to add a new note', () => { + cy.createWrapper(VnNotes, { + props: { + addNote: true, + }, + }); + cy.get('.note-input').type('Test note'); + cy.get('.submit-note-button').click(); + cy.get('@axiosStub').should('have.been.calledWithMatch', { + method: 'post', + url: '/api/notes', + }); + }); + + it('displays an error message if adding a note fails', () => { + cy.get('@axiosStub').throws(new Error('Network Error')); + cy.createWrapper(VnNotes, { + props: { + addNote: true, + }, + }); + cy.get('.note-input').type('Test note'); + cy.get('.submit-note-button').click(); + cy.get('.error-message').should('contain', 'Network Error'); + }); + + it('paginates notes correctly', () => { + cy.createWrapper(VnNotes); + cy.get('.pagination').should('exist'); + cy.get('.pagination .next-page').click(); + cy.get('@axiosStub').should('have.been.called'); + }); + + it('displays observation types when selectType prop is true', () => { + cy.createWrapper(VnNotes, { + props: { + selectType: true, + }, + }); + cy.get('.observation-type-select').should('be.visible'); + }); + + it('handles before route leave with unsaved changes', () => { + const onBeforeRouteLeave = cy.stub(); + cy.createWrapper(VnNotes, { + listeners: { + 'before-route-leave': onBeforeRouteLeave, + }, + }); + cy.get('.note-input').type('Unsaved note'); + cy.window().then((win) => { + win.dispatchEvent(new Event('beforeunload')); + expect(onBeforeRouteLeave).to.have.been.called; + }); }); }); diff --git a/test/cypress/components/ui/VnOutForm.spec.js b/test/cypress/components/ui/VnOutForm.spec.js index dfdaeea9a..c787c4986 100644 --- a/test/cypress/components/ui/VnOutForm.spec.js +++ b/test/cypress/components/ui/VnOutForm.spec.js @@ -1,8 +1,55 @@ -import VnOutForm from 'src/components/ui/VnOutForm.vue'; +// VnOutForm.spec.js +import VnOutForm from 'src/components/common/VnOutForm.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnOutForm); +describe.only('', () => { + const mountComponent = (props = {}, slots = {}) => { + cy.createWrapper(VnOutForm, { + props, + slots, + }); + }; + + it('renders without errors', () => { + mountComponent({ title: 'Test Title' }); + cy.get('.formCard').should('exist'); + }); + + it('displays the title prop', () => { + mountComponent({ title: 'Test Title' }); + cy.get('h5').should('contain', 'Test Title'); + }); + + it('renders the default icon when icon prop is not false', () => { + mountComponent({ title: 'Test Title' }); + cy.get('.q-icon').should('exist'); + cy.get('.q-icon').should('have.attr', 'name', 'phonelink_lock'); + }); + + it('does not render the icon when icon prop is false', () => { + mountComponent({ title: 'Test Title', icon: false }); + cy.get('.q-icon').should('not.exist'); + }); + + it('emits submit event when form is submitted', () => { + const onSubmit = cy.spy().as('submitSpy'); + mountComponent({ title: 'Test Title', onSubmit }); + cy.get('form').submit(); + cy.get('@submitSpy').should('have.been.called'); + }); + + it('renders default slot content', () => { + mountComponent( + { title: 'Test Title' }, + { default: '
Default Slot Content
' } + ); + cy.get('.slot-content').should('contain', 'Default Slot Content'); + }); + + it('renders buttons slot content', () => { + mountComponent( + { title: 'Test Title' }, + { buttons: '' } + ); + cy.get('.slot-buttons').should('contain', 'Button Slot Content'); }); }); diff --git a/test/cypress/components/ui/VnPaginate.spec.js b/test/cypress/components/ui/VnPaginate.spec.js index a986f5ca7..11520ac4d 100644 --- a/test/cypress/components/ui/VnPaginate.spec.js +++ b/test/cypress/components/ui/VnPaginate.spec.js @@ -1,8 +1,116 @@ -import VnPaginate from 'src/components/ui/VnPaginate.vue'; +// VnPaginate.spec.js +import VnPaginate from 'src/components/common/VnPaginate.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnPaginate); +describe.only('', () => { + beforeEach(() => { + cy.stub(window, 'axios').as('axiosStub'); + }); + + const mountComponent = (props = {}) => { + cy.createWrapper(VnPaginate, { + props: { + dataKey: 'test-key', + ...props, + }, + }); + }; + + it('renders without errors', () => { + mountComponent(); + cy.get('.vn-paginate').should('exist'); + }); + + it('loads data automatically when autoLoad is true', () => { + const url = '/api/test'; + mountComponent({ + url, + autoLoad: true, + }); + cy.get('@axiosStub').should('have.been.called'); + }); + + it('does not load data automatically when autoLoad is false', () => { + mountComponent({ + url: '/api/test', + autoLoad: false, + }); + cy.get('@axiosStub').should('not.have.been.called'); + }); + + it('handles static data array', () => { + const testData = [ + { id: 1, name: 'Test 1' }, + { id: 2, name: 'Test 2' }, + ]; + mountComponent({ + data: testData, + }); + cy.get('.vn-paginate').should('contain', 'Test 1'); + }); + + it('applies filters correctly', () => { + const testFilter = { search: 'test' }; + mountComponent({ + url: '/api/test', + filter: testFilter, + }); + cy.get('@axiosStub').should( + 'have.been.calledWith', + Cypress.sinon.match({ + params: Cypress.sinon.match(testFilter), + }) + ); + }); + + it('handles pagination events', () => { + mountComponent({ + url: '/api/test', + }); + cy.get('.pagination-next').click(); + cy.get('@axiosStub').should( + 'have.been.calledWith', + Cypress.sinon.match({ + params: Cypress.sinon.match({ + page: 2, + }), + }) + ); + }); + + it('applies custom classes', () => { + mountComponent({ + class: 'custom-class', + }); + cy.get('.vn-paginate').should('have.class', 'custom-class'); + }); + + it('handles user filters', () => { + const userFilter = { status: 'active' }; + mountComponent({ + url: '/api/test', + userFilter, + }); + cy.get('@axiosStub').should( + 'have.been.calledWith', + Cypress.sinon.match({ + params: Cypress.sinon.match(userFilter), + }) + ); + }); + + it('watches for filter changes and reloads data', () => { + // eslint-disable-next-line cypress/no-assigning-return-values + const wrapper = cy.createWrapper(VnPaginate, { + props: { + dataKey: 'test-key', + url: '/api/test', + }, + }); + + wrapper.setProps({ + filter: { newFilter: 'value' }, + }); + + cy.get('@axiosStub').should('have.been.calledTwice'); }); }); diff --git a/test/cypress/components/ui/VnRow.spec.js b/test/cypress/components/ui/VnRow.spec.js index 89ba138db..98d6b0a4b 100644 --- a/test/cypress/components/ui/VnRow.spec.js +++ b/test/cypress/components/ui/VnRow.spec.js @@ -1,8 +1,48 @@ -import VnRow from 'src/components/ui/VnRow.vue'; +// VnRow.spec.js +import VnRow from 'src/components/common/VnRow.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnRow); +describe.only('', () => { + const mountComponent = (props = {}, slots = {}) => { + cy.createWrapper(VnRow, { + props, + slots, + }); + }; + + it('renders without errors', () => { + mountComponent(); + cy.get('.vn-row').should('exist'); + }); + + it('applies flex display style', () => { + mountComponent(); + cy.get('.vn-row').should('have.css', 'display', 'flex'); + }); + + it('renders default slot content', () => { + const slotContent = '
Test Content
'; + mountComponent({}, { default: slotContent }); + cy.get('.vn-row .slot-content').should('contain', 'Test Content'); + }); + + it('applies wrap class when wrap prop is true', () => { + mountComponent({ wrap: true }); + cy.get('.vn-row').should('have.class', 'wrap'); + }); + + it('does not apply wrap class when wrap prop is false', () => { + mountComponent({ wrap: false }); + cy.get('.vn-row').should('not.have.class', 'wrap'); + }); + + it('child elements have flex: 1 style', () => { + const slotContent = ` +
Child 1
+
Child 2
+ `; + mountComponent({}, { default: slotContent }); + cy.get('.vn-row > *').each(($el) => { + cy.wrap($el).should('have.css', 'flex', '1 1 0%'); + }); }); }); diff --git a/test/cypress/components/ui/VnSearchbar.spec.js b/test/cypress/components/ui/VnSearchbar.spec.js index fd2ccb627..4ac8d3de1 100644 --- a/test/cypress/components/ui/VnSearchbar.spec.js +++ b/test/cypress/components/ui/VnSearchbar.spec.js @@ -1,8 +1,85 @@ -import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; +// VnSearchbar.spec.js +import VnSearchbar from 'src/components/common/VnSearchbar.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnSearchbar); +describe.only('', () => { + beforeEach(() => { + // Mock required composables and stores + cy.stub(window, 'useQuasar').returns({}); + cy.stub(window, 'useI18n').returns({ t: (key) => key }); + cy.stub(window, 'useStateStore').returns({}); + cy.stub(window, 'useArrayData').returns({}); + }); + + const mountComponent = (props = {}) => { + cy.createWrapper(VnSearchbar, { + props: { + dataKey: 'test-key', + ...props, + }, + }); + }; + + it('renders with default props', () => { + mountComponent(); + cy.getComponent('VnInput').should('exist'); + cy.get('input').should('have.attr', 'placeholder', 'Search'); + }); + + it('displays custom label', () => { + mountComponent({ label: 'Custom Search' }); + cy.get('input').should('have.attr', 'placeholder', 'Custom Search'); + }); + + it('shows info text when provided', () => { + mountComponent({ info: 'Search Info' }); + cy.get('.info-text').should('contain', 'Search Info'); + }); + + it('handles search input', () => { + mountComponent(); + cy.get('input').type('test search'); + cy.get('input').should('have.value', 'test search'); + }); + + it('triggers search with url when provided', () => { + const searchUrl = '/api/search'; + mountComponent({ url: searchUrl }); + cy.get('input').type('test{enter}'); + cy.get('@axiosStub').should( + 'have.been.calledWith', + Cypress.sinon.match({ + url: searchUrl, + }) + ); + }); + + it('handles redirect prop', () => { + const router = { + push: cy.spy().as('routerPush'), + }; + cy.stub(window, 'useRouter').returns(router); + + mountComponent({ redirect: true }); + cy.get('input').type('test{enter}'); + cy.get('@routerPush').should('have.been.called'); + }); + + it('applies custom filter', () => { + const customFilter = { category: 'test' }; + mountComponent({ filter: customFilter }); + cy.get('input').type('search{enter}'); + cy.get('@axiosStub').should( + 'have.been.calledWith', + Cypress.sinon.match({ + params: Cypress.sinon.match(customFilter), + }) + ); + }); + + it('clears search input', () => { + mountComponent(); + cy.get('input').type('test'); + cy.get('.clear-button').click(); + cy.get('input').should('have.value', ''); }); }); diff --git a/test/cypress/components/ui/VnSms.spec.js b/test/cypress/components/ui/VnSms.spec.js index 2a3ab6240..42d79dada 100644 --- a/test/cypress/components/ui/VnSms.spec.js +++ b/test/cypress/components/ui/VnSms.spec.js @@ -1,8 +1,69 @@ -import VnSms from 'src/components/ui/VnSms.vue'; +// VnSms.spec.js +import VnSms from 'src/components/common/VnSms.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + // Stub axios to prevent real HTTP requests + cy.stub(window, 'axios').as('axiosStub'); + }); + + it('renders without errors', () => { cy.createWrapper(VnSms); + cy.get('.vn-sms').should('exist'); + }); + + it('computes the correct filter based on props', () => { + const whereProp = { status: 'delivered' }; + cy.createWrapper(VnSms, { + props: { + where: whereProp, + }, + }); + cy.wrap(Cypress.vueWrapper.setupState.filter).then((filter) => { + expect(filter.value.where).to.deep.equal(whereProp); + expect(filter.value.fields).to.include('smsFk'); + expect(filter.value.include.relation).to.equal('sms'); + }); + }); + + it('displays SMS data correctly', () => { + const smsData = [ + { + smsFk: 1, + sms: { + senderFk: 1, + sender: 'Jane Smith', + destination: '+1234567890', + message: 'Test message content', + statusCode: 'sent', + status: 'Sent', + created: '2023-10-10T10:00:00Z', + // sender: { + // name: 'Jane Smith', + // }, + }, + }, + ]; + + cy.intercept('GET', '**/api/**', { body: smsData }).as('getSmsData'); + + cy.createWrapper(VnSms); + cy.wait('@getSmsData'); + + cy.get('.sms-message').should('contain', 'Test message content'); + cy.get('.sms-destination').should('contain', '+1234567890'); + }); + + it('handles pagination through VnPaginate', () => { + cy.createWrapper(VnSms); + cy.get('.vn-paginate').should('exist'); + cy.get('.paginate-next').click(); + cy.get('@axiosStub').should('have.been.called'); + }); + + it('renders VnAvatar and VnUserLink components', () => { + cy.createWrapper(VnSms); + cy.getComponent('VnAvatar').should('exist'); + cy.getComponent('VnUserLink').should('exist'); }); }); diff --git a/test/cypress/components/ui/VnSubToolbar.spec.js b/test/cypress/components/ui/VnSubToolbar.spec.js index 1fcc17f34..5a76239d9 100644 --- a/test/cypress/components/ui/VnSubToolbar.spec.js +++ b/test/cypress/components/ui/VnSubToolbar.spec.js @@ -1,8 +1,67 @@ -import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; +// VnSubToolbar.spec.js +import VnSubToolbar from 'src/components/common/VnSubToolbar.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue +describe.only('', () => { + beforeEach(() => { + // Mock store + cy.stub(window, 'useStateStore').returns({ + toggleSubToolbar: cy.stub().as('toggleSubToolbarStub'), + }); + + // Mock DOM elements + const actionDiv = document.createElement('div'); + actionDiv.id = 'st-actions'; + const dataDiv = document.createElement('div'); + dataDiv.id = 'st-data'; + document.body.appendChild(actionDiv); + document.body.appendChild(dataDiv); + }); + + afterEach(() => { + document.querySelector('#st-actions')?.remove(); + document.querySelector('#st-data')?.remove(); + }); + + it('renders toolbar correctly', () => { cy.createWrapper(VnSubToolbar); + cy.get('#subToolbar').should('exist'); + }); + + it('calls toggleSubToolbar on mount', () => { + cy.createWrapper(VnSubToolbar); + cy.get('@toggleSubToolbarStub').should('have.been.calledOnce'); + }); + + it('calls toggleSubToolbar on unmount', () => { + // eslint-disable-next-line cypress/no-assigning-return-values + const wrapper = cy.createWrapper(VnSubToolbar); + wrapper.unmount(); + cy.get('@toggleSubToolbarStub').should('have.been.calledTwice'); + }); + + it('updates hasContent when actions content changes', () => { + cy.createWrapper(VnSubToolbar); + const actionsEl = document.querySelector('#st-actions'); + const newChild = document.createElement('div'); + actionsEl.appendChild(newChild); + + cy.wrap(Cypress.vueWrapper.vm.hasContent).should('equal', 1); + }); + + it('updates hasContent when data content changes', () => { + cy.createWrapper(VnSubToolbar); + const dataEl = document.querySelector('#st-data'); + const newChild = document.createElement('div'); + dataEl.appendChild(newChild); + + cy.wrap(Cypress.vueWrapper.vm.hasContent).should('equal', 1); + }); + + it('handles missing action and data elements', () => { + document.querySelector('#st-actions')?.remove(); + document.querySelector('#st-data')?.remove(); + + cy.createWrapper(VnSubToolbar); + cy.wrap(Cypress.vueWrapper.vm.hasContent).should('equal', false); }); }); diff --git a/test/cypress/components/ui/VnUserLink.spec.js b/test/cypress/components/ui/VnUserLink.spec.js index d7e0c016d..0e596a714 100644 --- a/test/cypress/components/ui/VnUserLink.spec.js +++ b/test/cypress/components/ui/VnUserLink.spec.js @@ -1,8 +1,50 @@ import VnUserLink from 'src/components/ui/VnUserLink.vue'; -describe.skip('', () => { - it('TODO: boilerplate', () => { - // see: https://on.cypress.io/mounting-vue - cy.createWrapper(VnUserLink); +describe.only('', () => { + const mountComponent = (props = {}, slots = {}) => { + cy.createWrapper(VnUserLink, { + props, + slots, + }); + }; + + it('renders with default props', () => { + mountComponent(); + cy.get('span').should('exist'); + cy.get('span').should('not.have.class', 'link'); + cy.get('span').should('contain', ''); + }); + + it('displays name when provided and defaultName is false', () => { + mountComponent({ name: 'John Doe' }); + cy.get('span').should('contain', 'John Doe'); + }); + + it('displays default name when defaultName is true', () => { + mountComponent({ name: null, defaultName: true }); + cy.get('span').should('contain', 'globals.system'); + }); + + it('adds link class when workerId is provided', () => { + mountComponent({ workerId: 123 }); + cy.get('span').should('have.class', 'link'); + }); + + it('renders WorkerDescriptorProxy when workerId is provided', () => { + mountComponent({ workerId: 123 }); + cy.getComponent('WorkerDescriptorProxy').should('exist'); + cy.getComponent('WorkerDescriptorProxy') + .invoke('props') + .should('deep.include', { id: 123 }); + }); + + it('uses custom content from link slot', () => { + mountComponent( + {}, + { + link: 'Custom Link Content', + } + ); + cy.get('.custom-link').should('contain', 'Custom Link Content'); }); });