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 @@
-->
-
-
+
+