diff --git a/.gitignore b/.gitignore index 9f2f521a4..e8034c743 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ js/ build/ css/style.css css/vendor.css +cypress/videos/ tests/integration/vendor/ tests/integration/composer.lock tests/.phpunit.result.cache diff --git a/cypress.config.js b/cypress.config.js index dab8bd653..e619c6713 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -11,7 +11,6 @@ module.exports = defineConfig({ return require('./cypress/plugins/index.js')(on, config) }, baseUrl: 'http://nextcloud.local/index.php', - experimentalSessionAndOrigin: true, specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}', }, }) diff --git a/cypress/.eslintrc.js b/cypress/.eslintrc.js new file mode 100644 index 000000000..b8816e120 --- /dev/null +++ b/cypress/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + extends: [ + 'plugin:cypress/recommended', + ], +} diff --git a/cypress/e2e/boardFeatures.js b/cypress/e2e/boardFeatures.js index e2bdace64..acf37ac34 100644 --- a/cypress/e2e/boardFeatures.js +++ b/cypress/e2e/boardFeatures.js @@ -1,15 +1,15 @@ -import { randHash } from '../utils' -const randUser = randHash() +import { randUser } from '../utils/index.js' +const user = randUser() describe('Board', function() { - const password = 'pass123' before(function() { - cy.nextcloudCreateUser(randUser, password) + cy.createUser(user) }) beforeEach(function() { - cy.login(randUser, password) + cy.login(user) + cy.visit('/apps/deck') }) it('Can create a board', function() { diff --git a/cypress/e2e/cardFeatures.js b/cypress/e2e/cardFeatures.js index d5449440c..fbc994978 100644 --- a/cypress/e2e/cardFeatures.js +++ b/cypress/e2e/cardFeatures.js @@ -1,5 +1,5 @@ -import { randHash } from '../utils' -const randUser = randHash() +import { randUser } from '../utils/index.js' +const user = randUser() const testBoardData = { title: 'MyBoardTest', @@ -18,16 +18,18 @@ const testBoardData = { describe('Card', function() { before(function() { - cy.nextcloudCreateUser(randUser, randUser) + cy.createUser(user) + cy.login(user) cy.createExampleBoard({ - user: randUser, - password: randUser, + user: user.userId, + password: user.password, board: testBoardData, }) }) beforeEach(function() { - cy.login(randUser, randUser) + cy.login(user) + cy.visit('/apps/deck') }) it('Can show card details modal', function() { diff --git a/cypress/e2e/deckDashboard.js b/cypress/e2e/deckDashboard.js index f61e744e3..6cd65b6c4 100644 --- a/cypress/e2e/deckDashboard.js +++ b/cypress/e2e/deckDashboard.js @@ -1,21 +1,20 @@ -import { randHash } from '../utils' -const randUser = randHash() +import { randUser } from '../utils/index.js' +const user = randUser() describe('Deck dashboard', function() { - const password = 'pass123' - before(function() { - cy.nextcloudCreateUser(randUser, password) + cy.createUser(user) }) beforeEach(function() { - cy.login(randUser, password) + cy.login(user) + cy.visit('/apps/deck') }) it('Can show the right title on the dashboard', function() { cy.get('.board-title h2') - .should('have.length', 1).first() - .should('have.text', 'Upcoming cards') + .should('have.length', 1).first() + .should('have.text', 'Upcoming cards') }) it('Can see the default "Personal Board" created for user by default', function() { diff --git a/cypress/e2e/stackFeatures.js b/cypress/e2e/stackFeatures.js index 7754643ec..81f5dc721 100644 --- a/cypress/e2e/stackFeatures.js +++ b/cypress/e2e/stackFeatures.js @@ -1,30 +1,69 @@ -import { randHash } from '../utils' -const randUser = randHash() +import { randUser } from '../utils/index.js' +const user = randUser() + +const boardTitle = 'TestBoard' +const testBoardData = { + title: boardTitle, + stacks: [ + { title: 'Existing Stack1' }, + { title: 'Existing Stack2' }, + ], +} describe('Stack', function() { - const board = 'TestBoard' - const password = 'pass123' - const stack = 'List 1' before(function() { - cy.nextcloudCreateUser(randUser, password) - cy.deckCreateBoard({ user: randUser, password }, board) + cy.createUser(user) + cy.login(user) + cy.createExampleBoard({ + user: user.userId, + password: user.password, + board: testBoardData, + }) }) beforeEach(function() { - cy.logout() - cy.login(randUser, password) + cy.login(user) + cy.visit('/apps/deck') + + cy.openLeftSidebar() + cy.getNavigationEntry(boardTitle) + .click({ force: true }) }) it('Can create a stack', function() { - cy.openLeftSidebar() - cy.getNavigationEntry(board) - .click({ force: true }) - cy.get('#stack-add button').first().click() - cy.get('#stack-add form input#new-stack-input-main').type(stack) + cy.focused().type('List 1') cy.get('#stack-add form input[type=submit]').first().click() - cy.get('.board .stack').eq(0).contains(stack).should('be.visible') + cy.contains('List 1').should('be.visible') + }) + + it('Can edit a stack title', function() { + cy.contains('Existing Stack1') + cy.get('[data-cy-stack="Existing Stack1"]').within(() => { + cy.contains('Existing Stack1').click() + cy.focused().type(' renamed') + cy.get('[data-cy="editStackTitleForm"] input[type="submit"]').click() + }) + cy.contains('Existing Stack1 renamed').should('be.visible') + }) + + it('Can abort a stack title edit via esc', function() { + cy.contains('Existing Stack2').click() + cy.focused().type(' with a new title, maybe?') + cy.focused().type('{esc}') + + cy.contains('Existing Stack2').should('be.visible') + cy.contains('Existing Stack2 with a new title, maybe?').should('not.exist') + }) + + it('Can abort a stack title edit via click outside', function() { + cy.contains('Existing Stack2').click() + cy.focused().type(' with a new title, maybe?') + cy.get('[data-cy-stack="Existing Stack2"]').click('bottom') + + cy.contains('Existing Stack2').should('be.visible') + cy.contains('Existing Stack2 with a new title, maybe?').should('not.exist') }) }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index ff69e2344..f81ad73d4 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -20,61 +20,13 @@ * */ +import { addCommands } from '@nextcloud/cypress' + +addCommands() + const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '') Cypress.env('baseUrl', url) -Cypress.Commands.add('login', (user, password, route = '/apps/deck/') => { - const session = `${user}-${Date.now()}` - cy.session(session, function() { - cy.visit(route) - cy.get('input[name=user]').type(user) - cy.get('input[name=password]').type(password) - cy.get('form[name=login] [type=submit]').click() - cy.url().should('include', route) - }) - cy.visit(route) -}) - -Cypress.Commands.add('logout', (route = '/') => { - cy.session('_guest', function() {}) -}) - -Cypress.Commands.add('nextcloudCreateUser', (user, password) => { - cy.clearCookies() - cy.request({ - method: 'POST', - url: `${Cypress.env('baseUrl')}/ocs/v1.php/cloud/users?format=json`, - form: true, - body: { - userid: user, - password, - }, - auth: { user: 'admin', pass: 'admin' }, - headers: { - 'OCS-ApiRequest': 'true', - 'Content-Type': 'application/x-www-form-urlencoded', - }, - }).then((response) => { - cy.log(`Created user ${user}`, response.status) - }) -}) - -Cypress.Commands.add('nextcloudUpdateUser', (user, password, key, value) => { - cy.request({ - method: 'PUT', - url: `${Cypress.env('baseUrl')}/ocs/v2.php/cloud/users/${user}`, - form: true, - body: { key, value }, - auth: { user, pass: password }, - headers: { - 'OCS-ApiRequest': 'true', - 'Content-Type': 'application/x-www-form-urlencoded', - }, - }).then((response) => { - cy.log(`Updated user ${user} ${key} to ${value}`, response.status) - }) -}) - Cypress.Commands.add('openLeftSidebar', () => { cy.get('.app-navigation button.app-navigation-toggle').click() }) diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index d68db96df..b6ca34662 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -14,7 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import './commands.js' // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/cypress/utils/index.js b/cypress/utils/index.js index aa46b9f07..034de4d2e 100644 --- a/cypress/utils/index.js +++ b/cypress/utils/index.js @@ -1 +1,4 @@ +import { User } from '@nextcloud/cypress' + export const randHash = () => Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 10) +export const randUser = () => new User(randHash(), randHash()) diff --git a/package-lock.json b/package-lock.json index 80494637c..13af3235f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,12 +47,14 @@ "devDependencies": { "@nextcloud/babel-config": "^1.0.0", "@nextcloud/browserslist-config": "^2.3.0", + "@nextcloud/cypress": "^1.0.0-beta.2", "@nextcloud/eslint-config": "^8.1.4", "@nextcloud/stylelint-config": "^2.3.0", "@nextcloud/webpack-vue-config": "^5.4.0", "@relative-ci/agent": "^4.1.3", "@vue/test-utils": "^1.3.3", "cypress": "^12.2.0", + "eslint-plugin-cypress": "^2.12.1", "eslint-webpack-plugin": "^3.2.0", "jest": "^29.3.1", "jest-serializer-vue": "^3.1.0", @@ -3017,6 +3019,19 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/@nextcloud/cypress": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@nextcloud/cypress/-/cypress-1.0.0-beta.2.tgz", + "integrity": "sha512-IWxs0/S2SQA+lSG4Hla8kF/LEMO7lTBC3cY5bsY6V+MDhtUoHmq6SI3OVurirFwfYx4BoC+XSZzybKjzaZDb1w==", + "dev": true, + "engines": { + "node": "^16.0.0", + "npm": "^7.0.0 || ^8.0.0" + }, + "peerDependencies": { + "cypress": "^12.0.1" + } + }, "node_modules/@nextcloud/dialogs": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-3.2.0.tgz", @@ -7879,6 +7894,18 @@ "dev": true, "peer": true }, + "node_modules/eslint-plugin-cypress": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz", + "integrity": "sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==", + "dev": true, + "dependencies": { + "globals": "^11.12.0" + }, + "peerDependencies": { + "eslint": ">= 3.2.1" + } + }, "node_modules/eslint-plugin-es": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", @@ -9673,9 +9700,10 @@ } }, "node_modules/globals": { - "version": "11.9.0", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -20756,6 +20784,13 @@ } } }, + "@nextcloud/cypress": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@nextcloud/cypress/-/cypress-1.0.0-beta.2.tgz", + "integrity": "sha512-IWxs0/S2SQA+lSG4Hla8kF/LEMO7lTBC3cY5bsY6V+MDhtUoHmq6SI3OVurirFwfYx4BoC+XSZzybKjzaZDb1w==", + "dev": true, + "requires": {} + }, "@nextcloud/dialogs": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-3.2.0.tgz", @@ -24810,6 +24845,15 @@ } } }, + "eslint-plugin-cypress": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz", + "integrity": "sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==", + "dev": true, + "requires": { + "globals": "^11.12.0" + } + }, "eslint-plugin-es": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", @@ -25873,7 +25917,9 @@ } }, "globals": { - "version": "11.9.0", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, "globby": { diff --git a/package.json b/package.json index 340a95cea..f45c01dea 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "watch": "NODE_ENV=development webpack --progress --watch --config webpack.js", "lint": "eslint --ext .js,.vue src", "lint:fix": "eslint --ext .js,.vue src --fix", + "lint:cypress": "eslint --ext .js cypress", "stylelint": "stylelint src", "stylelint:fix": "stylelint src --fix", "test": "jest", @@ -73,12 +74,14 @@ "devDependencies": { "@nextcloud/babel-config": "^1.0.0", "@nextcloud/browserslist-config": "^2.3.0", + "@nextcloud/cypress": "^1.0.0-beta.2", "@nextcloud/eslint-config": "^8.1.4", "@nextcloud/stylelint-config": "^2.3.0", "@nextcloud/webpack-vue-config": "^5.4.0", "@relative-ci/agent": "^4.1.3", "@vue/test-utils": "^1.3.3", "cypress": "^12.2.0", + "eslint-plugin-cypress": "^2.12.1", "eslint-webpack-plugin": "^3.2.0", "jest": "^29.3.1", "jest-serializer-vue": "^3.1.0", diff --git a/src/components/board/Stack.vue b/src/components/board/Stack.vue index 6e585e33a..a1f9c5df5 100644 --- a/src/components/board/Stack.vue +++ b/src/components/board/Stack.vue @@ -22,7 +22,7 @@ -->