From 8b53111748b92de5943639f06c915e59951106f4 Mon Sep 17 00:00:00 2001 From: valentinab25 Date: Wed, 26 May 2021 12:59:19 +0300 Subject: [PATCH 1/2] Cypress coverage --- .coverage.babel.config.js | 9 + Jenkinsfile | 93 ++++++-- bootstrap | 17 +- cypress.json | 1 + cypress/fixtures/example.json | 5 + cypress/integration/block-basics.js | 32 +++ cypress/plugins/index.js | 26 +++ cypress/support/commands.js | 315 ++++++++++++++++++++++++++++ cypress/support/index.js | 53 +++++ package.json | 13 +- 10 files changed, 536 insertions(+), 28 deletions(-) create mode 100644 .coverage.babel.config.js create mode 100644 cypress/fixtures/example.json create mode 100644 cypress/integration/block-basics.js create mode 100644 cypress/plugins/index.js create mode 100644 cypress/support/commands.js create mode 100644 cypress/support/index.js diff --git a/.coverage.babel.config.js b/.coverage.babel.config.js new file mode 100644 index 0000000..e8b54d3 --- /dev/null +++ b/.coverage.babel.config.js @@ -0,0 +1,9 @@ +const defaultBabel = require('@plone/volto/babel'); + +function applyDefault(api) { + const voltoBabel = defaultBabel(api); + voltoBabel.plugins.push('@babel/plugin-transform-modules-commonjs', 'transform-class-properties', 'istanbul'); + return voltoBabel; +} + +module.exports = applyDefault; diff --git a/Jenkinsfile b/Jenkinsfile index c48ab4d..df9bf5a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,6 +5,7 @@ pipeline { GIT_NAME = "volto-group-block" NAMESPACE = "@eeacms" SONARQUBE_TAGS = "volto.eea.europa.eu" + DEPENDENCIES = "" } stages { @@ -44,13 +45,13 @@ pipeline { try { sh '''docker pull plone/volto-addon-ci''' sh '''docker run -i --name="$BUILD_TAG-volto" -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" plone/volto-addon-ci''' + sh '''rm -rf xunit-reports''' sh '''mkdir -p xunit-reports''' sh '''docker cp $BUILD_TAG-volto:/opt/frontend/my-volto-project/coverage xunit-reports/''' sh '''docker cp $BUILD_TAG-volto:/opt/frontend/my-volto-project/junit.xml xunit-reports/''' sh '''docker cp $BUILD_TAG-volto:/opt/frontend/my-volto-project/unit_tests_log.txt xunit-reports/''' - stash name: "xunit-reports", includes: "xunit-reports/**/*" - junit 'xunit-reports/junit.xml' - archiveArtifacts artifacts: 'xunit-reports/unit_tests_log.txt', fingerprint: true + stash name: "xunit-reports", includes: "xunit-reports/**" + archiveArtifacts artifacts: "xunit-reports/unit_tests_log.txt", fingerprint: true publishHTML (target : [ allowMissing: false, alwaysLinkToLastBuild: true, @@ -61,7 +62,10 @@ pipeline { reportTitles: 'Unit Tests Code Coverage' ]) } finally { - sh '''docker rm -v $BUILD_TAG-volto''' + catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') { + junit testResults: 'xunit-reports/junit.xml', allowEmptyResults: true + } + sh script: '''docker rm -v $BUILD_TAG-volto''', returnStatus: true } } } @@ -70,6 +74,53 @@ pipeline { } } + stage('Integration tests') { + steps { + parallel( + + "Cypress": { + node(label: 'docker') { + script { + try { + sh '''docker pull plone; docker run -d --name="$BUILD_TAG-plone" -e SITE="Plone" -e PROFILES="profile-plone.restapi:blocks" plone fg''' + sh '''docker pull plone/volto-addon-ci; docker run -i --name="$BUILD_TAG-cypress" --link $BUILD_TAG-plone:plone -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e DEPENDENCIES="$DEPENDENCIES" plone/volto-addon-ci cypress''' + } finally { + try { + sh '''rm -rf cypress-reports cypress-results cypress-coverage''' + sh '''mkdir -p cypress-reports cypress-results cypress-coverage''' + sh '''docker cp $BUILD_TAG-cypress:/opt/frontend/my-volto-project/src/addons/$GIT_NAME/cypress/videos cypress-reports/''' + sh '''docker cp $BUILD_TAG-cypress:/opt/frontend/my-volto-project/src/addons/$GIT_NAME/cypress/reports cypress-results/''' + coverage = sh script: '''docker cp $BUILD_TAG-cypress:/opt/frontend/my-volto-project/src/addons/$GIT_NAME/coverage cypress-coverage/''', returnStatus: true + if ( coverage == 0 ) { + publishHTML (target : [allowMissing: false, + alwaysLinkToLastBuild: true, + keepAll: true, + reportDir: 'cypress-coverage/coverage/lcov-report', + reportFiles: 'index.html', + reportName: 'CypressCoverage', + reportTitles: 'Integration Tests Code Coverage']) + } + archiveArtifacts artifacts: 'cypress-reports/videos/*.mp4', fingerprint: true + stash name: "cypress-coverage", includes: "cypress-coverage/**", allowEmpty: true + } + finally { + catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') { + junit testResults: 'cypress-results/**/*.xml', allowEmptyResults: true + } + sh script: "docker stop $BUILD_TAG-plone", returnStatus: true + sh script: "docker rm -v $BUILD_TAG-plone", returnStatus: true + sh script: "docker rm -v $BUILD_TAG-cypress", returnStatus: true + + } + } + } + } + } + + ) + } + } + stage('Report to SonarQube') { // Exclude Pull-Requests when { @@ -82,11 +133,12 @@ pipeline { script{ checkout scm unstash "xunit-reports" + unstash "cypress-coverage" def scannerHome = tool 'SonarQubeScanner'; def nodeJS = tool 'NodeJS11'; withSonarQubeEnv('Sonarqube') { sh '''sed -i "s#/opt/frontend/my-volto-project/src/addons/${GIT_NAME}/##g" xunit-reports/coverage/lcov.info''' - sh "export PATH=$PATH:${scannerHome}/bin:${nodeJS}/bin; sonar-scanner -Dsonar.javascript.lcov.reportPaths=./xunit-reports/coverage/lcov.info -Dsonar.sources=./src -Dsonar.coverage.exclusions=src/**/*.test.js -Dsonar.projectKey=$GIT_NAME-$BRANCH_NAME -Dsonar.projectVersion=$BRANCH_NAME-$BUILD_NUMBER" + sh "export PATH=$PATH:${scannerHome}/bin:${nodeJS}/bin; sonar-scanner -Dsonar.javascript.lcov.reportPaths=./xunit-reports/coverage/lcov.info,./cypress-coverage/coverage/lcov.info -Dsonar.sources=./src -Dsonar.projectKey=$GIT_NAME-$BRANCH_NAME -Dsonar.projectVersion=$BRANCH_NAME-$BUILD_NUMBER" sh '''try=2; while [ \$try -gt 0 ]; do curl -s -XPOST -u "${SONAR_AUTH_TOKEN}:" "${SONAR_HOST_URL}api/project_tags/set?project=${GIT_NAME}-${BRANCH_NAME}&tags=${SONARQUBE_TAGS},${BRANCH_NAME}" > set_tags_result; if [ \$(grep -ic error set_tags_result ) -eq 0 ]; then try=0; else cat set_tags_result; echo "... Will retry"; sleep 60; try=\$(( \$try - 1 )); fi; done''' } } @@ -109,7 +161,7 @@ pipeline { } withCredentials([string(credentialsId: 'eea-jenkins-token', variable: 'GITHUB_TOKEN')]) { sh '''docker pull eeacms/gitflow''' - sh '''docker run -i --rm --name="$BUILD_TAG-gitflow-pr" -e GIT_CHANGE_TARGET="$CHANGE_TARGET" -e GIT_CHANGE_BRANCH="$CHANGE_BRANCH" -e GIT_CHANGE_AUTHOR="$CHANGE_AUTHOR" -e GIT_CHANGE_TITLE="$CHANGE_TITLE" -e GIT_TOKEN="$GITHUB_TOKEN" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e GIT_ORG="$GIT_ORG" -e GIT_NAME="$GIT_NAME" eeacms/gitflow''' + sh '''docker run -i --rm --name="$BUILD_TAG-gitflow-pr" -e GIT_CHANGE_TARGET="$CHANGE_TARGET" -e GIT_CHANGE_BRANCH="$CHANGE_BRANCH" -e GIT_CHANGE_AUTHOR="$CHANGE_AUTHOR" -e GIT_CHANGE_TITLE="$CHANGE_TITLE" -e GIT_TOKEN="$GITHUB_TOKEN" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e GIT_ORG="$GIT_ORG" -e GIT_NAME="$GIT_NAME" -e LANGUAGE=javascript eeacms/gitflow''' } } } @@ -127,7 +179,7 @@ pipeline { node(label: 'docker') { withCredentials([string(credentialsId: 'eea-jenkins-token', variable: 'GITHUB_TOKEN'),string(credentialsId: 'eea-jenkins-npm-token', variable: 'NPM_TOKEN')]) { sh '''docker pull eeacms/gitflow''' - sh '''docker run -i --rm --name="$BUILD_TAG-gitflow-master" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_NAME="$GIT_NAME" -e GIT_TOKEN="$GITHUB_TOKEN" -e NPM_TOKEN="$NPM_TOKEN" eeacms/gitflow''' + sh '''docker run -i --rm --name="$BUILD_TAG-gitflow-master" -e GIT_BRANCH="$BRANCH_NAME" -e GIT_NAME="$GIT_NAME" -e GIT_TOKEN="$GITHUB_TOKEN" -e NPM_TOKEN="$NPM_TOKEN" -e LANGUAGE=javascript eeacms/gitflow''' } } } @@ -136,24 +188,21 @@ pipeline { } post { + always { + cleanWs(cleanWhenAborted: true, cleanWhenFailure: true, cleanWhenNotBuilt: true, cleanWhenSuccess: true, cleanWhenUnstable: true, deleteDirs: true) + } changed { script { - def url = "${env.BUILD_URL}/display/redirect" - def status = currentBuild.currentResult - def subject = "${status}: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'" - def summary = "${subject} (${url})" - def details = """

${env.JOB_NAME} - Build #${env.BUILD_NUMBER} - ${status}

-

Check console output at ${env.JOB_BASE_NAME} - #${env.BUILD_NUMBER}

+ def details = """

${env.JOB_NAME} - Build #${env.BUILD_NUMBER} - ${currentBuild.currentResult}

+

Check console output at ${env.JOB_BASE_NAME} - #${env.BUILD_NUMBER}

""" - - def color = '#FFFF00' - if (status == 'SUCCESS') { - color = '#00FF00' - } else if (status == 'FAILURE') { - color = '#FF0000' - } - - emailext (subject: '$DEFAULT_SUBJECT', to: '$DEFAULT_RECIPIENTS', body: details) + emailext( + subject: '$DEFAULT_SUBJECT', + body: details, + attachLog: true, + compressLog: true, + recipientProviders: [[$class: 'DevelopersRecipientProvider'], [$class: 'CulpritsRecipientProvider']] + ) } } } diff --git a/bootstrap b/bootstrap index 8b2c40f..8613750 100644 --- a/bootstrap +++ b/bootstrap @@ -1,5 +1,6 @@ const path = require('path'); const fs = require('fs'); +const ejs = require('ejs'); const currentDir = path.basename(process.cwd()); @@ -8,13 +9,23 @@ const bootstrap = function (ofile) { if (err) { return console.log(err); } - var result = data.replace(/volto-addon-template/g, currentDir); - - fs.writeFile(ofile, result, 'utf8', function (err) { + const result = ejs.render(data, { + addonName: `@eeacms/${currentDir}`, + name: currentDir + }); + const output = ofile.replace('.tpl', ''); + fs.writeFile(output, result, 'utf8', function (err) { if (err) { return console.log(err); } }); + if (ofile.includes('.tpl')) { + fs.unlink(ofile, (err) => { + if (err) { + return console.error(err); + } + }); + } }); } diff --git a/cypress.json b/cypress.json index 23eedae..aef675e 100644 --- a/cypress.json +++ b/cypress.json @@ -6,6 +6,7 @@ "video": true, "reporterOptions": { "mochaFile": "cypress/reports/cypress-[hash].xml", + "jenkinsMode": true, "toConsole": true } } diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..da18d93 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} \ No newline at end of file diff --git a/cypress/integration/block-basics.js b/cypress/integration/block-basics.js new file mode 100644 index 0000000..454084c --- /dev/null +++ b/cypress/integration/block-basics.js @@ -0,0 +1,32 @@ +import { setupBeforeEach, tearDownAfterEach } from '../support'; + +describe('Blocks Tests', () => { + beforeEach(setupBeforeEach); + afterEach(tearDownAfterEach); + + it('Add Block: Empty', () => { + // Change page title + cy.get('.documentFirstHeading > .public-DraftStyleDefault-block') + .clear() + .type('My Add-on Page') + .get('.documentFirstHeading span[data-text]') + .contains('My Add-on Page'); + + cy.get('.documentFirstHeading > .public-DraftStyleDefault-block').type( + '{enter}', + ); + + // Add block + cy.get('.ui.basic.icon.button.block-add-button').first().click(); + cy.get('.blocks-chooser .title').contains('Media').click(); + cy.get('.content.active.media .button.image').contains('Image').click(); + + // Save + cy.get('#toolbar-save').click(); + cy.url().should('eq', Cypress.config().baseUrl + '/cypress/my-page'); + + // then the page view should contain our changes + cy.contains('My Add-on Page'); + cy.get('.block.image'); + }); +}); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 0000000..27a31a5 --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,26 @@ +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config + /* coverage-start + require('@cypress/code-coverage/task')(on, config) + on('file:preprocessor', require('@cypress/code-coverage/use-babelrc')) + return config + coverage-end */ +}; diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000..ac48461 --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,315 @@ +/* eslint no-console: ["error", { allow: ["log"] }] */ + +// --- AUTOLOGIN ------------------------------------------------------------- +Cypress.Commands.add('autologin', () => { + let api_url, user, password; + api_url = Cypress.env('API_PATH') || 'http://localhost:8080/Plone'; + user = 'admin'; + password = 'admin'; + + return cy + .request({ + method: 'POST', + url: `${api_url}/@login`, + headers: { Accept: 'application/json' }, + body: { login: user, password: password }, + }) + .then((response) => cy.setCookie('auth_token', response.body.token)); +}); + +// --- CREATE CONTENT -------------------------------------------------------- +Cypress.Commands.add( + 'createContent', + ({ + contentType, + contentId, + contentTitle, + path = '', + allow_discussion = false, + }) => { + let api_url, auth; + api_url = Cypress.env('API_PATH') || 'http://localhost:8080/Plone'; + auth = { + user: 'admin', + pass: 'admin', + }; + if (contentType === 'File') { + return cy.request({ + method: 'POST', + url: `${api_url}/${path}`, + headers: { + Accept: 'application/json', + }, + auth: auth, + body: { + '@type': contentType, + id: contentId, + title: contentTitle, + file: { + data: 'dGVzdGZpbGUK', + encoding: 'base64', + filename: 'lorem.txt', + 'content-type': 'text/plain', + }, + allow_discussion: allow_discussion, + }, + }); + } + if (contentType === 'Image') { + return cy.request({ + method: 'POST', + url: `${api_url}/${path}`, + headers: { + Accept: 'application/json', + }, + auth: auth, + body: { + '@type': contentType, + id: contentId, + title: contentTitle, + image: { + data: + 'iVBORw0KGgoAAAANSUhEUgAAANcAAAA4CAMAAABZsZ3QAAAAM1BMVEX29fK42OU+oMvn7u9drtIPisHI4OhstdWZyt4fkcXX5+sAg74umMhNp86p0eJ7vNiKw9v/UV4wAAAAAXRSTlMAQObYZgAABBxJREFUeF7tmuty4yAMhZG4X2zn/Z92J5tsBJwWXG/i3XR6frW2Y/SBLIRAfaQUDNt8E5tLUt9BycfcKfq3R6Mlfyimtx4rzp+K3dtibXkor99zsEqLYZltblTecciogoh+TXfY1Ve4dn07rCDGG9dHSEEOg/GmXl0U1XDxTKxNK5De7BxsyyBr6gGm2/vPxKJ8F6f7BXKfRMp1xIWK9A+5ks25alSb353dWnDJN1k35EL5f8dVGifTf/4tjUuuFq7u4srmXC60yAmldLXIWbg65RKU87lcGxJCFqUPv0IacW0PmSivOZFLE908inPToMmii/roG+MRV/O8FU88i8tFsxV3a06MFUw0Qu7RmAtdV5/HVVaOVMTWNOWSwMljLhzhcB6XIS7OK5V6AvRDNN7t5VJWQs1J40UmalbK56usBG/CuCHSYuc+rkUGeMCViNRARPrzW52N3oQLe6WifNliSuuGaH3czbVNudI9s7ZLUCLHVwWlyES522o1t14uvmbblmVTKqFjaZYJFSTPP4dLL1kU1z7p0lzdbRulmEWLxoQX+z9ce7A8GqEEucllLxePuZwdJl1Lezu0hoswvTPt61DrFcRuujV/2cmlxaGBC7Aw6cpovGANwRiSdOAWJ5AGy4gLL64dl0QhUEAuEUNws+XxV+OKGPdw/hESGYF9XEGaFC7sNLMSXWJjHsnanYi87VK428N2uxpOjOFANcagLM5l+7mSycM8KknZpKLcGi6jmzWGr/vLurZ/0g4u9AZuAoeb5r1ceQhyiTPY1E4wUR6u/F3H2ojSpXMMriBPT9cezTto8Cx+MsglHL4fv1Rxrb1LVw9yvyQpJ3AhFnLZfuRLH2QsOG3FGGD20X/th/u5bFAt16Bt308KjF+MNOXgl/SquIEySX3GhaZvc67KZbDxcCDORz2N8yCWPaY5lyQZO7lQ29fnZbt3Xu6qoge4+DjXl/MocySPOp9rlvdyznahRyHEYd77v3LhugOXDv4J65QXfl803BDAdaWBEDhfVx7nKofjoVCgxnUAqw/UAUDPn788BDvQuG4TDtdtUPvzjSlXAB8DvaDOhhrmhwbywylXAm8CvaouikJTL93gs3y7Yy4VYbIxOHrcMizPqWOjqO9l3Uz52kibQy4xxOgqhJvD+w5rvokOcAlGvNCfeqCv1ste1stzLm0f71Iq3ZfTrPfuE5nhPtF+LvQE2lffQC7pYtQy3tdzdrKvd5TLVVzDetScS3nEKmmwDyt1Cev1kX3YfbvzNK4fzrlw+cB6vm+uiUgf2zdXI62241LawCb7Pi5FXFPF8KpzDoF/Sw2lg+GrHNbno1mhPu+VCF/vfMnw06PnUl6j48dVHD3jHNHPua+fc3o/5yp/zsGi0vYtzi3Pz5mHd4T6BWMIlewacd63AAAAAElFTkSuQmCC', + encoding: 'base64', + filename: 'image.png', + 'content-type': 'image/png', + }, + }, + }); + } + if (['Document', 'Folder', 'CMSFolder'].includes(contentType)) { + return cy + .request({ + method: 'POST', + url: `${api_url}/${path}`, + headers: { + Accept: 'application/json', + }, + auth: auth, + body: { + '@type': contentType, + id: contentId, + title: contentTitle, + blocks: { + 'd3f1c443-583f-4e8e-a682-3bf25752a300': { '@type': 'title' }, + '7624cf59-05d0-4055-8f55-5fd6597d84b0': { '@type': 'text' }, + }, + blocks_layout: { + items: [ + 'd3f1c443-583f-4e8e-a682-3bf25752a300', + '7624cf59-05d0-4055-8f55-5fd6597d84b0', + ], + }, + allow_discussion: allow_discussion, + }, + }) + .then(() => console.log(`${contentType} created`)); + } else { + return cy + .request({ + method: 'POST', + url: `${api_url}/${path}`, + headers: { + Accept: 'application/json', + }, + auth: auth, + body: { + '@type': contentType, + id: contentId, + title: contentTitle, + allow_discussion: allow_discussion, + }, + }) + .then(() => console.log(`${contentType} created`)); + } + }, +); + +// --- REMOVE CONTENT -------------------------------------------------------- +Cypress.Commands.add('removeContent', (path) => { + let api_url, auth; + api_url = Cypress.env('API_PATH') || 'http://localhost:8080/Plone'; + auth = { + user: 'admin', + pass: 'admin', + }; + return cy + .request({ + method: 'DELETE', + url: `${api_url}/${path}`, + headers: { + Accept: 'application/json', + }, + auth: auth, + body: {}, + }) + .then(() => console.log(`${path} removed`)); +}); + +// --- SET WORKFLOW ---------------------------------------------------------- +Cypress.Commands.add( + 'setWorkflow', + ({ + path = '/', + actor = 'admin', + review_state = 'publish', + time = '1995-07-31T18:30:00', + title = '', + comment = '', + effective = '2018-01-21T08:00:00', + expires = '2019-01-21T08:00:00', + include_children = true, + }) => { + let api_url, auth; + api_url = Cypress.env('API_PATH') || 'http://localhost:8080/Plone'; + auth = { + user: 'admin', + pass: 'admin', + }; + return cy.request({ + method: 'POST', + url: `${api_url}/${path}/@workflow/${review_state}`, + headers: { + Accept: 'application/json', + }, + auth: auth, + body: { + actor: actor, + review_state: review_state, + time: time, + title: title, + comment: comment, + effective: effective, + expires: expires, + include_children: include_children, + }, + }); + }, +); + +// --- waitForResourceToLoad ---------------------------------------------------------- +Cypress.Commands.add('waitForResourceToLoad', (fileName, type) => { + const resourceCheckInterval = 40; + + return new Cypress.Promise((resolve) => { + const checkIfResourceHasBeenLoaded = () => { + const resource = cy + .state('window') + .performance.getEntriesByType('resource') + .filter((entry) => !type || entry.initiatorType === type) + .find((entry) => entry.name.includes(fileName)); + + if (resource) { + resolve(); + + return; + } + + setTimeout(checkIfResourceHasBeenLoaded, resourceCheckInterval); + }; + + checkIfResourceHasBeenLoaded(); + }); +}); + +// Low level command reused by `setSelection` and low level command `setCursor` +Cypress.Commands.add('selection', { prevSubject: true }, (subject, fn) => { + cy.wrap(subject).trigger('mousedown').then(fn).trigger('mouseup'); + + cy.document().trigger('selectionchange'); + return cy.wrap(subject); +}); + +Cypress.Commands.add( + 'setSelection', + { prevSubject: true }, + (subject, query, endQuery) => { + return cy.wrap(subject).selection(($el) => { + if (typeof query === 'string') { + const anchorNode = getTextNode($el[0], query); + const focusNode = endQuery ? getTextNode($el[0], endQuery) : anchorNode; + const anchorOffset = anchorNode.wholeText.indexOf(query); + const focusOffset = endQuery + ? focusNode.wholeText.indexOf(endQuery) + endQuery.length + : anchorOffset + query.length; + setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset); + } else if (typeof query === 'object') { + const el = $el[0]; + const anchorNode = getTextNode(el.querySelector(query.anchorQuery)); + const anchorOffset = query.anchorOffset || 0; + const focusNode = query.focusQuery + ? getTextNode(el.querySelector(query.focusQuery)) + : anchorNode; + const focusOffset = query.focusOffset || 0; + setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset); + } + }); + }, +); + +// Low level command reused by `setCursorBefore` and `setCursorAfter`, equal to `setCursorAfter` +Cypress.Commands.add( + 'setCursor', + { prevSubject: true }, + (subject, query, atStart) => { + return cy.wrap(subject).selection(($el) => { + const node = getTextNode($el[0], query); + const offset = + node.wholeText.indexOf(query) + (atStart ? 0 : query.length); + const document = node.ownerDocument; + document.getSelection().removeAllRanges(); + document.getSelection().collapse(node, offset); + }); + // Depending on what you're testing, you may need to chain a `.click()` here to ensure + // further commands are picked up by whatever you're testing (this was required for Slate, for example). + }, +); + +Cypress.Commands.add( + 'setCursorBefore', + { prevSubject: true }, + (subject, query) => { + cy.wrap(subject).setCursor(query, true); + }, +); + +Cypress.Commands.add( + 'setCursorAfter', + { prevSubject: true }, + (subject, query) => { + cy.wrap(subject).setCursor(query); + }, +); + +// Helper functions +function getTextNode(el, match) { + const walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false); + if (!match) { + return walk.nextNode(); + } + + let node; + while ((node = walk.nextNode())) { + if (node.wholeText.includes(match)) { + return node; + } + } +} + +function setBaseAndExtent(...args) { + const document = args[0].ownerDocument; + document.getSelection().removeAllRanges(); + document.getSelection().setBaseAndExtent(...args); +} + +Cypress.Commands.add('navigate', (route = '') => { + return cy.window().its('appHistory').invoke('push', route); +}); + +Cypress.Commands.add('store', () => { + return cy.window().its('store').invoke('getStore', ''); +}); + +Cypress.Commands.add('settings', (key, value) => { + return cy.window().its('settings'); +}); diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 0000000..a3fd935 --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,53 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; + +// Alternatively you can use CommonJS syntax: +// require('./commands') + +/* coverage-start +//Generate code-coverage +import '@cypress/code-coverage/support'; +coverage-end */ + +export const setupBeforeEach = () => { + cy.autologin(); + cy.createContent({ + contentType: 'Folder', + contentId: 'cypress', + contentTitle: 'Cypress', + }); + cy.createContent({ + contentType: 'Document', + contentId: 'my-page', + contentTitle: 'My Page', + path: 'cypress', + }); + cy.visit('/cypress/my-page'); + cy.waitForResourceToLoad('@navigation'); + cy.waitForResourceToLoad('@breadcrumbs'); + cy.waitForResourceToLoad('@actions'); + cy.waitForResourceToLoad('@types'); + cy.waitForResourceToLoad('my-page'); + cy.navigate('/cypress/my-page/edit'); + cy.get(`.block.title [data-contents]`); +}; + +export const tearDownAfterEach = () => { + cy.autologin(); + cy.removeContent('cypress'); +}; diff --git a/package.json b/package.json index efde04d..5a63155 100644 --- a/package.json +++ b/package.json @@ -18,16 +18,23 @@ "type": "git", "url": "git@github.com:eea/volto-group-block.git" }, - "dependencies": {}, + "dependencies": { + }, + "devDependencies": { + "@cypress/code-coverage": "^3.9.5", + "babel-plugin-transform-class-properties": "^6.24.1" + }, "scripts": { "release": "release-it", - "bootstrap": "node bootstrap", + "bootstrap": "npm install -g ejs; npm link ejs; node bootstrap", "stylelint": "../../../node_modules/stylelint/bin/stylelint.js --allow-empty-input 'src/**/*.{css,less}'", "stylelint:overrides": "../../../node_modules/.bin/stylelint --syntax less --allow-empty-input 'theme/**/*.overrides' 'src/**/*.overrides'", "stylelint:fix": "yarn stylelint --fix && yarn stylelint:overrides --fix", "prettier": "../../../node_modules/.bin/prettier --single-quote --check 'src/**/*.{js,jsx,json,css,less,md}'", "prettier:fix": "../../../node_modules/.bin/prettier --single-quote --write 'src/**/*.{js,jsx,json,css,less,md}'", "lint": "../../../node_modules/eslint/bin/eslint.js --max-warnings=0 'src/**/*.{js,jsx}'", - "lint:fix": "../../../node_modules/eslint/bin/eslint.js --fix 'src/**/*.{js,jsx}'" + "lint:fix": "../../../node_modules/eslint/bin/eslint.js --fix 'src/**/*.{js,jsx}'", + "cypress:run": "../../../node_modules/cypress/bin/cypress run", + "cypress:open": "../../../node_modules/cypress/bin/cypress open" } } From 9982cde20ea5675e5d6b978f6eb4aed1cfc3258e Mon Sep 17 00:00:00 2001 From: EEA Jenkins <@users.noreply.github.com> Date: Wed, 26 May 2021 10:06:28 +0000 Subject: [PATCH 2/2] Automated release 4.2.3 --- CHANGELOG.md | 8 +++++++- package.json | 5 ++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e3c21a..75abec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,15 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [4.2.3](https://github.com/eea/volto-group-block/compare/4.2.2...4.2.3) + +- Cypress coverage [`8b53111`](https://github.com/eea/volto-group-block/commit/8b53111748b92de5943639f06c915e59951106f4) + #### [4.2.2](https://github.com/eea/volto-group-block/compare/4.2.1...4.2.2) -- [JENKINS] - Auto-release pipeline [`8be997b`](https://github.com/eea/volto-group-block/commit/8be997b1ff6218da6253b24ae69f67584bdb5074) +> 14 May 2021 + +- [JENKINS] - Auto-release pipeline [`#7`](https://github.com/eea/volto-group-block/pull/7) #### [4.2.1](https://github.com/eea/volto-group-block/compare/4.2.0...4.2.1) diff --git a/package.json b/package.json index 5a63155..38a75a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@eeacms/volto-group-block", - "version": "4.2.2", + "version": "4.2.3", "description": "volto-group-block: Volto block to be used to group other blocks", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", @@ -18,8 +18,7 @@ "type": "git", "url": "git@github.com:eea/volto-group-block.git" }, - "dependencies": { - }, + "dependencies": {}, "devDependencies": { "@cypress/code-coverage": "^3.9.5", "babel-plugin-transform-class-properties": "^6.24.1"