diff --git a/cypress/component/NcAppSettingsDialog.cy.ts b/cypress/component/NcAppSettingsDialog.cy.ts new file mode 100644 index 0000000000..ffeba0ae4f --- /dev/null +++ b/cypress/component/NcAppSettingsDialog.cy.ts @@ -0,0 +1,48 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { mount } from 'cypress/vue2' +import NcAppSettingsDialog from '../../src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue' +import NcAppSettingsSection from '../../src/components/NcAppSettingsSection/NcAppSettingsSection.vue' +import { defineComponent } from 'vue' + +describe('NcAppSettingsDialog', () => { + it('Dialog is correctly labelled', () => { + mount(NcAppSettingsDialog, { + propsData: { + open: true, + name: 'My settings dialog', + }, + slots: { + default: defineComponent({ + render: (h) => h(NcAppSettingsSection, { props: { name: 'First section', id: 'first' } }) + }) + }, + }) + + cy.findByRole('dialog', { name: 'My settings dialog' }).should('exist') + }) + + it('Dialog sections are correctly labelled', () => { + mount(NcAppSettingsDialog, { + propsData: { + open: true, + name: 'My settings dialog', + showNavigation: true, + }, + slots: { + default: defineComponent({ + render: (h) => h(NcAppSettingsSection, { props: { name: 'First section', id: 'first' } }, ['The section content']) + }) + }, + }) + + cy.findByRole('dialog', { name: 'My settings dialog' }).should('exist') + cy.findByRole('dialog', { name: 'My settings dialog' }) + .findByRole('region', { name: 'First section' }) + .should('exist') + .and('contain.text', 'The section content') + }) +}) diff --git a/cypress/component/NcDialog.cy.ts b/cypress/component/NcDialog.cy.ts new file mode 100644 index 0000000000..300590e684 --- /dev/null +++ b/cypress/component/NcDialog.cy.ts @@ -0,0 +1,23 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { mount } from 'cypress/vue2' +import NcDialog from '../../src/components/NcDialog/NcDialog.vue' + +describe('NcDialog', () => { + it('Dialog is correctly labelled', () => { + mount(NcDialog, { + propsData: { + show: true, + name: 'My dialog', + }, + slots: { + default: 'Text', + }, + }) + + cy.findByRole('dialog', { name: 'My dialog' }).should('exist') + }) +}) diff --git a/cypress/component/NcModal.cy.ts b/cypress/component/NcModal.cy.ts new file mode 100644 index 0000000000..ac65237111 --- /dev/null +++ b/cypress/component/NcModal.cy.ts @@ -0,0 +1,79 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { mount } from 'cypress/vue2' +import NcModal from '../../src/components/NcModal/NcModal.vue' +import type { Component } from 'vue' + +describe('NcModal', () => { + it('Modal is labelled correctly if name is set', () => { + mount(NcModal, { + propsData: { + show: true, + name: 'My modal', + size: 'small', + }, + slots: { + default: 'Text', + }, + }) + + cy.findByRole('dialog', { name: 'My modal' }).should('exist') + }) + + it('Modal is labelled correctly if `labelId` is set', () => { + mount(NcModal, { + propsData: { + show: true, + size: 'small', + labelId: 'my-id', + }, + slots: { + default: '

Labelled dialog

', + }, + }) + + cy.findByRole('dialog', { name: 'Labelled dialog' }).should('exist') + }) + + it('Modal is labelled correctly if `labelId` and `name` are set', () => { + mount(NcModal, { + propsData: { + show: true, + size: 'small', + name: 'My modal', + labelId: 'my-id', + }, + slots: { + default: '

Real name

', + }, + }) + + cy.findByRole('dialog', { name: 'Real name' }).should('exist') + }) + + it('close button is visible when content is scrolled', () => { + mount(NcModal, { + propsData: { + show: true, + size: 'small', + name: 'Name', + }, + slots: { + // Create two div as children, first is 100vh = overflows the content, second just gets some data attribute so we can scroll into view + default: { + render: (h) => + h('div', [ + h('div', { style: 'height: 100vh;' }), + h('div', { attrs: { 'data-cy': 'bottom' } }), + ]), + } as Component, + }, + }) + + cy.get('[data-cy="bottom"]').scrollIntoView() + cy.get('button.modal-container__close').should('be.visible') + }) +}) diff --git a/cypress/component/modal.cy.ts b/cypress/component/modal.cy.ts deleted file mode 100644 index a1e361d529..0000000000 --- a/cypress/component/modal.cy.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { mount } from 'cypress/vue2' -import NcModal from '../../src/components/NcModal/NcModal.vue' -import type { Component } from 'vue' - -describe('NcModal', () => { - it('close button is visible when content is scrolled', () => { - mount(NcModal, { - propsData: { - show: true, - size: 'small', - name: 'Name', - }, - slots: { - // Create two div as children, first is 100vh = overflows the content, second just gets some data attribute so we can scroll into view - default: { - render: (h) => - h('div', [ - h('div', { style: 'height: 100vh;' }), - h('div', { attrs: { 'data-cy': 'bottom' } }), - ]), - } as Component, - }, - }) - - cy.get('[data-cy="bottom"]').scrollIntoView() - cy.get('button.modal-container__close').should('be.visible') - }) -}) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 1cad81ca4d..729eb70cef 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -5,4 +5,6 @@ import { addCompareSnapshotCommand } from 'cypress-visual-regression/dist/command' +import '@testing-library/cypress/add-commands' + addCompareSnapshotCommand() diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 07a68c258d..b5864bb932 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -4,7 +4,8 @@ "compilerOptions": { "types": [ "cypress", - "cypress-visual-regression" + "cypress-visual-regression", + "@testing-library/cypress" ] } } diff --git a/package-lock.json b/package-lock.json index 9531482b7c..6796950965 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "@nextcloud/stylelint-config": "^3.0.0", "@nextcloud/vite-config": "^1.2.2", "@nextcloud/webpack-vue-config": "^6.0.1", + "@testing-library/cypress": "^10.0.2", "@types/gettext-parser": "^4.0.4", "@types/jest": "^29.5.5", "@vue/test-utils": "^1.3.0", @@ -4854,6 +4855,144 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@testing-library/cypress": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-10.0.2.tgz", + "integrity": "sha512-dKv95Bre5fDmNb9tOIuWedhGUryxGu1GWYWtXDqUsDPcr9Ekld0fiTb+pcBvSsFpYXAZSpmyEjhoXzLbhh06yQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.14.6", + "@testing-library/dom": "^10.1.0" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "cypress": "^12.0.0 || ^13.0.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz", + "integrity": "sha512-pT/TYB2+IyMYkkB6lqpkzD7VFbsR0JBJtflK3cS68sCNWxmOhWwRm1XvVHlseNEorsNcxkYsb4sRDV3aNIpttg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -4893,6 +5032,12 @@ "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", "dev": true }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -6665,6 +6810,15 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -10957,6 +11111,12 @@ "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", "dev": true }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, "node_modules/dom-event-types": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/dom-event-types/-/dom-event-types-1.1.0.tgz", @@ -18929,6 +19089,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.10", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", diff --git a/package.json b/package.json index 54009c8104..4f56615ce9 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "@nextcloud/stylelint-config": "^3.0.0", "@nextcloud/vite-config": "^1.2.2", "@nextcloud/webpack-vue-config": "^6.0.1", + "@testing-library/cypress": "^10.0.2", "@types/gettext-parser": "^4.0.4", "@types/jest": "^29.5.5", "@vue/test-utils": "^1.3.0", diff --git a/src/components/NcAppSettingsSection/NcAppSettingsSection.vue b/src/components/NcAppSettingsSection/NcAppSettingsSection.vue index df67f729a5..f987945368 100644 --- a/src/components/NcAppSettingsSection/NcAppSettingsSection.vue +++ b/src/components/NcAppSettingsSection/NcAppSettingsSection.vue @@ -4,14 +4,14 @@ -->