From 1a07672a8f614c993df61e1de451859f5b8f41ce Mon Sep 17 00:00:00 2001 From: Wolfgang Meier Date: Sun, 26 Apr 2020 20:46:37 +0200 Subject: [PATCH] Increment included versions of existdb-launcher and existdb-usermanager once more --- bower.json | 6 +- bower_components/existdb-launcher/.bower.json | 10 +- .../existdb-launcher/existdb-login.html | 7 + .../existdb-usermanager/.bower.json | 10 +- .../existdb-usermanager/controller.xql | 19 +- .../existdb-usermanager/cypress.json | 4 + .../cypress/fixtures/example.json | 5 + .../cypress/integration/4.x.x/users_spec.js | 30 + .../cypress/integration/5.x.x/users_spec.js | 30 + .../cypress/plugins/index.js | 17 + .../cypress/support/README.md | 420 ++ .../cypress/support/commands.js | 25 + .../cypress/support/index.js | 24 + .../cypress/support/shadowCommands.js | 683 ++ .../cypress/support/shadowDom_spec.js | 100 + .../cypress/support/shadowSelect.js | 70 + .../cypress/support/shadowShould.js | 27 + .../cypress/support/smartlog.js | 15 + .../cypress/support/waitsfor.js | 32 + .../existdb-usermanager.html | 11 +- .../modules/userManager.xqm | 21 +- .../existdb-usermanager/package-lock.json | 5733 +++++++++++++++++ 22 files changed, 7270 insertions(+), 29 deletions(-) create mode 100644 bower_components/existdb-usermanager/cypress.json create mode 100644 bower_components/existdb-usermanager/cypress/fixtures/example.json create mode 100644 bower_components/existdb-usermanager/cypress/integration/4.x.x/users_spec.js create mode 100644 bower_components/existdb-usermanager/cypress/integration/5.x.x/users_spec.js create mode 100644 bower_components/existdb-usermanager/cypress/plugins/index.js create mode 100644 bower_components/existdb-usermanager/cypress/support/README.md create mode 100644 bower_components/existdb-usermanager/cypress/support/commands.js create mode 100644 bower_components/existdb-usermanager/cypress/support/index.js create mode 100644 bower_components/existdb-usermanager/cypress/support/shadowCommands.js create mode 100644 bower_components/existdb-usermanager/cypress/support/shadowDom_spec.js create mode 100644 bower_components/existdb-usermanager/cypress/support/shadowSelect.js create mode 100644 bower_components/existdb-usermanager/cypress/support/shadowShould.js create mode 100644 bower_components/existdb-usermanager/cypress/support/smartlog.js create mode 100644 bower_components/existdb-usermanager/cypress/support/waitsfor.js create mode 100644 bower_components/existdb-usermanager/package-lock.json diff --git a/bower.json b/bower.json index 83597c08..d516f4e7 100644 --- a/bower.json +++ b/bower.json @@ -16,8 +16,8 @@ "paper-item": "PolymerElements/paper-item#^2.0.0", "app-route": "PolymerElements/app-route#^2.0.0", "paper-ripple": "PolymerElements/paper-ripple#^2.0.0", - "existdb-launcher": "https://github.com/eXist-db/launcher.git#^1.2.3", - "existdb-usermanager": "https://github.com/eXist-db/usermanager.git#^v0.16.6", + "existdb-launcher": "https://github.com/eXist-db/launcher.git#^1.2.4", + "existdb-usermanager": "https://github.com/eXist-db/usermanager.git#^v0.16.7", "existdb-backup": "https://github.com/eXist-db/existdb-backup.git#^1.1.0", "repo-elements": "https://github.com/eXist-db/repo-elements/archive/master.zip", "iron-lazy-pages": "^2.1.1", @@ -35,4 +35,4 @@ "resolutions": { "polymer": "^2.0.0" } -} \ No newline at end of file +} diff --git a/bower_components/existdb-launcher/.bower.json b/bower_components/existdb-launcher/.bower.json index 874f1784..8eebe1f9 100644 --- a/bower_components/existdb-launcher/.bower.json +++ b/bower_components/existdb-launcher/.bower.json @@ -41,14 +41,14 @@ ], "private": true, "homepage": "https://github.com/eXist-db/launcher", - "version": "1.2.2", - "_release": "1.2.2", + "version": "1.2.4", + "_release": "1.2.4", "_resolution": { "type": "version", - "tag": "v1.2.2", - "commit": "780d30183d78736cac060622cc18a8fd50ad8351" + "tag": "v1.2.4", + "commit": "b9b0e574d7026b5ca836b019599fc098f4116129" }, "_source": "https://github.com/eXist-db/launcher.git", - "_target": "^1.2.2", + "_target": "^1.2.4", "_originalSource": "https://github.com/eXist-db/launcher.git" } \ No newline at end of file diff --git a/bower_components/existdb-launcher/existdb-login.html b/bower_components/existdb-launcher/existdb-login.html index ea42c1f9..ec702bce 100644 --- a/bower_components/existdb-launcher/existdb-login.html +++ b/bower_components/existdb-launcher/existdb-login.html @@ -176,6 +176,13 @@

Login

this.$.checkLogin.generateRequest(); } }); + document.addEventListener('checkUser', (ev) => { + console.log('checkUser event received: %o', ev.detail); + if (ev.detail.user === this.user) { + this.password = ev.detail.password; + this._confirmLogin(); + } + }); } ready(){ diff --git a/bower_components/existdb-usermanager/.bower.json b/bower_components/existdb-usermanager/.bower.json index 765e0a88..67a92b72 100644 --- a/bower_components/existdb-usermanager/.bower.json +++ b/bower_components/existdb-usermanager/.bower.json @@ -51,14 +51,14 @@ ], "private": true, "homepage": "https://github.com/eXist-db/usermanager", - "version": "0.16.5", - "_release": "0.16.5", + "version": "0.16.7", + "_release": "0.16.7", "_resolution": { "type": "version", - "tag": "v0.16.5", - "commit": "ae449609b86e863591a91cbf6aa06c8e65d2590d" + "tag": "v0.16.7", + "commit": "5897bf0403e20c8202a678025e9c618678cdc45b" }, "_source": "https://github.com/eXist-db/usermanager.git", - "_target": "^v0.16.5", + "_target": "^v0.16.7", "_originalSource": "https://github.com/eXist-db/usermanager.git" } \ No newline at end of file diff --git a/bower_components/existdb-usermanager/controller.xql b/bower_components/existdb-usermanager/controller.xql index 3bc69010..f9e76a57 100755 --- a/bower_components/existdb-usermanager/controller.xql +++ b/bower_components/existdb-usermanager/controller.xql @@ -139,8 +139,8 @@ declare function local:delete-group($group as xs:string) as element(deleted) { declare function local:update-user($user as xs:string, $request-body as xs:string) as element() { if(usermanager:update-user($user, jsjson:parse-json($request-body)))then ( - response:set-header("Location", local:get-user-location($user)), - response:set-status-code($local:HTTP_OK), + (: response:set-header("Location", local:get-user-location($user)), + response:set-status-code($local:HTTP_OK), :) (: TODO ideally would like to set 204 above and not return and content in the body however the controller.xql is not capable of doing that, as there is no dispatch/ignore @@ -158,8 +158,8 @@ declare function local:update-user($user as xs:string, $request-body as xs:strin declare function local:update-group($group as xs:string, $request-body) as element() { if(usermanager:update-group($group, jsjson:parse-json($request-body)))then ( - response:set-header("Location", local:get-group-location($group)), - response:set-status-code($local:HTTP_OK), + (: response:set-header("Location", local:get-group-location($group)), + response:set-status-code($local:HTTP_OK), :) (: TODO ideally would like to set 204 above and not return and content in the body however the controller.xql is not capable of doing that, as there is no dispatch/ignore @@ -180,8 +180,8 @@ declare function local:create-user($user as xs:string, $request-body) as element if($user)then ( - response:set-header("Location", local:get-user-location($user)), - response:set-status-code($local:HTTP_CREATED), + (: response:set-header("Location", local:get-user-location($user)), + response:set-status-code($local:HTTP_CREATED), :) (: send back updated user json :) usermanager:get-user($user) @@ -195,8 +195,8 @@ declare function local:create-group($user as xs:string, $request-body) as elemen let $group := usermanager:create-group(jsjson:parse-json($request-body)) return if($group)then ( - response:set-header("Location", local:get-group-location($group)), - response:set-status-code($local:HTTP_CREATED), + (: response:set-header("Location", local:get-group-location($group)), + response:set-status-code($local:HTTP_CREATED), :) (: send back updated group json :) usermanager:get-group($group) @@ -226,13 +226,10 @@ declare function local:get-group($group as xs:string) as element() { ) }; -(: let $log := util:log("info", "exist path: " || $exist:path) let $log := util:log("info", "request URI: " || request:get-uri()) let $log := util:log("info", "resource: " || $exist:resource) return -:) - if ($exist:path eq '') then diff --git a/bower_components/existdb-usermanager/cypress.json b/bower_components/existdb-usermanager/cypress.json new file mode 100644 index 00000000..bbf3e91f --- /dev/null +++ b/bower_components/existdb-usermanager/cypress.json @@ -0,0 +1,4 @@ +{ + "baseUrl": "http://localhost:8080/exist/apps", + "projectId": "6d2b23" +} diff --git a/bower_components/existdb-usermanager/cypress/fixtures/example.json b/bower_components/existdb-usermanager/cypress/fixtures/example.json new file mode 100644 index 00000000..da18d935 --- /dev/null +++ b/bower_components/existdb-usermanager/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/bower_components/existdb-usermanager/cypress/integration/4.x.x/users_spec.js b/bower_components/existdb-usermanager/cypress/integration/4.x.x/users_spec.js new file mode 100644 index 00000000..164fafaf --- /dev/null +++ b/bower_components/existdb-usermanager/cypress/integration/4.x.x/users_spec.js @@ -0,0 +1,30 @@ +/// + +describe('User Manager', function() { + describe('log in', function() { + before(function() { + cy.visit('/dashboard/login.html') + cy.get('#user').type('admin') + cy.get('.button').click() + }) + describe('create and delete users', function() { + it('should create and delete users', function() { + cy.visit('/dashboard/admin#/usermanager') + cy.shadowGet('#addUser').shadowClick() + .shadowGet('#userInput').shadowTrigger('input', 'rudi') + cy.shadowGet('input[type=password]').shadowTrigger('input', 'pass') + cy.shadowGet('#confirm').shadowTrigger('input', 'pass') + cy.shadowGet('#memberOf > paper-item:nth-child(1)').shadowClick() + cy.shadowGet('#save').shadowClick() + cy.shadowGet('.user-entry .user:contains("rudi")').shadowShould('have.length', 1) + + cy.shadowGet('.user-entry .user:contains("rudi")').shadowClick() + cy.shadowGet('#userForm > form > paper-button:nth-child(11)').shadowClick().wait(500) + + cy.shadowGet('.user-entry') + .shadowFind('span.user') + .shadowShould('not.have.text', 'rudi') + }) + }) + }) +}) diff --git a/bower_components/existdb-usermanager/cypress/integration/5.x.x/users_spec.js b/bower_components/existdb-usermanager/cypress/integration/5.x.x/users_spec.js new file mode 100644 index 00000000..164fafaf --- /dev/null +++ b/bower_components/existdb-usermanager/cypress/integration/5.x.x/users_spec.js @@ -0,0 +1,30 @@ +/// + +describe('User Manager', function() { + describe('log in', function() { + before(function() { + cy.visit('/dashboard/login.html') + cy.get('#user').type('admin') + cy.get('.button').click() + }) + describe('create and delete users', function() { + it('should create and delete users', function() { + cy.visit('/dashboard/admin#/usermanager') + cy.shadowGet('#addUser').shadowClick() + .shadowGet('#userInput').shadowTrigger('input', 'rudi') + cy.shadowGet('input[type=password]').shadowTrigger('input', 'pass') + cy.shadowGet('#confirm').shadowTrigger('input', 'pass') + cy.shadowGet('#memberOf > paper-item:nth-child(1)').shadowClick() + cy.shadowGet('#save').shadowClick() + cy.shadowGet('.user-entry .user:contains("rudi")').shadowShould('have.length', 1) + + cy.shadowGet('.user-entry .user:contains("rudi")').shadowClick() + cy.shadowGet('#userForm > form > paper-button:nth-child(11)').shadowClick().wait(500) + + cy.shadowGet('.user-entry') + .shadowFind('span.user') + .shadowShould('not.have.text', 'rudi') + }) + }) + }) +}) diff --git a/bower_components/existdb-usermanager/cypress/plugins/index.js b/bower_components/existdb-usermanager/cypress/plugins/index.js new file mode 100644 index 00000000..fd170fba --- /dev/null +++ b/bower_components/existdb-usermanager/cypress/plugins/index.js @@ -0,0 +1,17 @@ +// *********************************************************** +// 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) + +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/bower_components/existdb-usermanager/cypress/support/README.md b/bower_components/existdb-usermanager/cypress/support/README.md new file mode 100644 index 00000000..407e3dd3 --- /dev/null +++ b/bower_components/existdb-usermanager/cypress/support/README.md @@ -0,0 +1,420 @@ + +# ShadowDom + +* [Quick Start](#quick-start) +* [About](#about) +* [API](#api) + +## Quick Start + +In the **index.js** or the root file of your cypress/support folder, +import and register cypress commands + +```js +import { registerShadowCommands } from '@updater/cypress-helpers'; + +registerShadowCommands(); +``` + +Example + +```js + cy + .get('#homeServicesContainer') + .shadowGet('upd-home-services-landing-page-route') + .shadowFind('#title') + .shadowShould('have.text', 'Connect TV & Internet'); +``` + +## About + +Cypress does not have native support for `shadowDom`. +This module gives us the ability to run similar commands in shadow DOM. +These commands are not as fully developed as the native ones, +but resemble native Cypress commands in usage. + +> Only use these commands on elements *within* a `shadowRoot`, +not on elements which may have a `shadowRoot`. + +What to Expect +* Commands should feel familiar as Cypress ones and behave in similar ways +* There is automatic retrying for certain commands (e.g. `shadowGet` and `shadowShould`) +* Non-Dom results can be yielded into regular Cypress commands. +For example: + +```js +cy.shadowGet('some-shadow-element') + .then($el => $el.text()) + .should('match', /*.?/g); // no need to build non-DOM interactors! +``` + +The main differences are +* Limited API use / less supported features +* Retrying is on a per command (not per chain) basis (except for `shadowGet`, +which does support retrying upcoming assertions) +* No extra visibility/attachment/covered/disabled checks on `click` and `trigger` +* Potentially others...TBD + + ## API + +* [`registerShadowCommands`](#registershadowcommands) +* [`shadowGet`](#shadowget) +* [`shadowFind`](#shadowfind) +* [`shadowShould`](#shadowshould) +* [`shadowEq`](#shadoweq) +* [`shadowClick`](#shadowclick) +* [`shadowSelect`](#shadowselect) +* [`shadowTrigger`](#shadowtrigger) +* [`shadowContains`](#shadowcontains) + + + +*** + +## registerShadowCommands + +Register shadow commands on the `cy` namespace, +such as `shadowGet`, `shadowShould`, etc. + +*** + +## shadowGet + +**Retryable / Async** + +* deeply searches DOM for all elements which have a `shadowRoot` +* queries (with jquery) each `shadowRoot` with provided selector +* Returns all matching results +* Subject is optional + + +| Param | Type | Description | +| --- | --- | --- | +| selector | string | jquery selector used to find matching elements | +| [options] | object | the options to modify behaviour of the command | +| [options.timeout] | number | number of ms to retry the query until marking the command as failed. Defaults to 4s or the `defaultCommandTimeout` in cypress.config. | + +**Example** +### Syntax + +```js +.shadowGet(selector) +.shadowGet(selector, options) +``` + +### Usage + +```js +// searches entire DOM +cy.shadowGet('custom-el-in-shadow') + +// searches only within container-el +cy.get('container-el') + .shadowGet('custom-el-in-shadow') + +// waits up to 30s (resolve immediately when found) +cy.shadowGet('custom-el-in-shadow', { timeout: 30000 }) + +``` +*** + +## shadowFind + +**Retryable / Async** + +* must be chained from another command (e.g. `shadowGet` or `get`) +* queries (via jquery) the yielded element and the yielded element's `shadowRoot` for matches +* **Only** searches within the `shadowRoot` of the yielded element (as well as just the regular DOM children) +* **Note** it is a **shallow** search within the yielded elements shadowRoot. It will **not** +do a deep search through shadowRoots for the matching element. For deep search, use `shadowGet` + +> You may wonder why a shallow search is needed. That's because in shadowDom +> Unique selectors like `id`s can be repeated. Sometimes we just want to search +> in the immediate root without worrying about coliding with things further down +> the DOM tree. + + +| Param | Type | Description | +| --- | --- | --- | +| selector | string | jquery selector used to find matching elements | +| [options] | object | the options to modify behaviour of the command | +| [options.timeout] | number | number of ms to retry the query until marking the command as failed. Defaults to 4s or the `defaultCommandTimeout` in cypress.config. | + +**Example** +### Syntax + +```js +.shadowFind(selector) +.shadowFind(selector, options) +``` + +### Usage + +```js +// shadowFind queries against the subjects' children and shadowRoot +cy.get('container-el') + .shadowFind('button.action') + +// shadowGet matches from all shadowRoots +// shadowFind queries against the subjects' children and shadowRoot +cy.shadowGet('custom-el-in-shadow') + .shadowFind('button.action') // will query against the subject's children and shadowRoot +``` +*** + +## shadowShould + +**Retryable / Async** +(Up to 4s, timeout not customizable) + +This Utility is most useful when needing to run +assertions against shadowDom elements and it does so by leveraging `jquery-chai`. `Cypress` also does this, but it does not work in shadowDom. +* it accepts the `string` syntax like Cypress' `should` +* it does not accept the `function` syntax +(but you can still use `should` with shadow elements as long as you run non `jquery-chai` assertions) +* This smooths over the issues with Cypress' `jquery-chai`, +which does explicit checks that are incompatible with shadowDom. +* It uses a clean version of jquery and chai to run assertions against shadowDom elements. +* In general, you can use `should` as long as you do not need to assert against the shadow DOM. + +> **When should I use `shadowShould` and when should I use `should`?** +> +> Use `shadowShould` whenever you need to run assertions + against elements within the `shadowDom`. +> Lite DOM and regular DOM can be used with `should`. +> Also, any non-DOM values can be used with `should`. +> You can do something like, +> +>`.should(($el) => expect($el.text()).to.match(/.?/))` +> +> Or even, +> +> `.then(($el) => $el.text()).should('match', /.?/))`. +> +> These are examples of taking non-DOM values from the shadowDom elements and using +> regular Cypress commands and assertions on them. + + +| Param | Type | Description | +| --- | --- | --- | +| chainer | string | the string | +| value | any | the value to be checked | +| method | string | the sub-chainer to check (see example) | + +**Example** +### Syntax + +```js +.shadowShould(chainers) +.shadowShould(chainers, value) +.shadowShould(chainers, method, value) +``` + +### Usage + +```js +cy.get('@dateLabel') + .should('have.text', '2017-11-22'); + +cy.get('@datepicker') + .shadowFind('button[value="2017-11-22"]') + .shadowShould('have.attr', 'tabindex', '0'); +``` +*** + +## shadowEq + +**No-Retry / Sync** + +Yields a subject at the index from a given subject. + +For example, if this is chained from a `shadowGet` +which yields multiple elements, `shadowEq` will return +the element at the specified index. + +It will *not* retry and yields whatever is passed +into it synchronously. + + +| Param | Type | Description | +| --- | --- | --- | +| index | number | specifies the index of the element to yield from the subject | + +**Example** +### Syntax + +```js +.shadowEq(selector) +``` + +### Usage + +```js +cy.get('container-el') + .shadowFind('button.action') + .shadowEq(2) + +cy.shadowGet('custom-el-in-shadow') + .shadowEq(4) +``` +*** + +## shadowClick + +**No-Retry / Sync** + +* Allows you to click on an element within a shadowRoot. +* Can be chained from `shadowGet`, `shadowFind`, or `shadowEq` +* Clicks on the first element (index 0) from the +yielded elements of previous command +* Cypress' `click` does not work in shadowDom for multiple reasons +* Uses native or jquery .click functionality, +but does not do additional checks Cypress' click does +such as checking the component is visible, +not covered, and not disabled. +* Would need to put in more work to ensure component clicks cannot pass +through when the component is not in an actual interactive state. + +**Example** +### Syntax + +```js +.shadowClick() +``` + +### Usage + +```js +cy.get('container-el') + .shadowGet('custom-element-within-shadow-dom') + .shadowFind('button.action') + .shadowClick() +``` +*** + +## shadowSelect + +**No-Retry / Sync** + +* Allows you to select an option from a `select` element within a shadowRoot. +* Can be chained from `shadowGet`, `shadowFind`, or `shadowEq` +* Expects an actual `select` element to be the subject +* Selects the provided `option` from the first element (index 0) from the +yielded elements of previous command +* Option can be by `value` or by `text`, but must be strictly equal +* Cypress' `select` does not work in shadowDom for multiple reasons +* Does not do additional checks Cypress' select does +such as checking the component is visible, +not covered, and not disabled. +* Would need to put in more work to ensure component selects cannot pass +through when the component is not in an actual interactive state. + + +| Param | Type | Description | +| --- | --- | --- | +| option | String \| Number | The option from the `select` to select, by `value` or by `text` | + +**Example** +### Syntax + +```js +.shadowSelect(option) +``` + +### Usage + +```js + cy + .shadowGet('upd-select[name="state"]') + .shadowFind('select') + .shadowSelect('AL'); // by value + + cy + .shadowGet('upd-select[name="bedrooms"]') + .shadowFind('select') + .shadowSelect('3 Bedroooms'); // by text +``` +*** + +## shadowTrigger + +**No Retry / Sync** + +* allows to trigger an event similarly to how Cypress' `trigger` works. +* This works with elements on the shadow DOM since they pose problems with +almost all of Cypress' commands. +* Currently only supports these events: + * `keydown` + * `keypress` + * `keyup` + * `change` + * `input` +* Options can also be provided per event + * `key` events supports keyboard event options i.e. `keyCode` or `bubbles` + * `change` and `input` events support the `value` for the update + + +| Param | Type | Description | +| --- | --- | --- | +| event | string | The event from the above list which will be triggered | +| options | object \| value | Options which depend on the kind of event and modify the event's behaviour | + +**Example** +```js +.shadowTrigger(event) +.shadowTrigger(event, options) +``` + +### Usage + +```js +// Changing an input +cy + .shadowGet('upd-input[name="postalCode"]') + .shadowFind('input') + .shadowTrigger('input', '99511'); + +// changing value of a custom element +cy + .shadowGet('upd-datepicker') + .shadowTrigger('change', '2019-01-02'); + +// triggering a key event +cy.get('@datepicker') + .shadowFind('button[aria-selected="true"]') + .shadowTrigger('keydown', { keyCode: 39, bubbles: true }); +``` +*** + +## shadowContains + +**No-Retry / Sync** + +Convenience function to assert partial match between the `textContent` of +an element and the passed in value. + +This does not work like `cy.contains`. + +Literally just runs this assertion: +```js + expect(subject[0].textContent).to.contain(text) +``` + + +| Param | Type | Description | +| --- | --- | --- | +| text | string | the text to match against | + +**Example** +```js +.shadowContains(text) +``` + +### Usage + +```js + cy + .shadowGet('some-custom-elem') + .shadowContains('Should contain this text...') +``` +*File located at [/src/shadowDom/shadowCommands.js](/src/shadowDom/shadowCommands.js)* diff --git a/bower_components/existdb-usermanager/cypress/support/commands.js b/bower_components/existdb-usermanager/cypress/support/commands.js new file mode 100644 index 00000000..c1f5a772 --- /dev/null +++ b/bower_components/existdb-usermanager/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This is will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/bower_components/existdb-usermanager/cypress/support/index.js b/bower_components/existdb-usermanager/cypress/support/index.js new file mode 100644 index 00000000..274f78b2 --- /dev/null +++ b/bower_components/existdb-usermanager/cypress/support/index.js @@ -0,0 +1,24 @@ +// *********************************************************** +// 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' + +import { registerShadowCommands } from './shadowCommands'; + +registerShadowCommands(); + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/bower_components/existdb-usermanager/cypress/support/shadowCommands.js b/bower_components/existdb-usermanager/cypress/support/shadowCommands.js new file mode 100644 index 00000000..bc2cf6cf --- /dev/null +++ b/bower_components/existdb-usermanager/cypress/support/shadowCommands.js @@ -0,0 +1,683 @@ +const Promise = require('bluebird'); + +import { merge } from 'lodash'; +import { waitsFor, catchRejection } from './waitsfor'; +import { smartlog } from './smartlog'; +import { should, ensureChainerExists } from './shadowShould'; +import { shadowSelect as _shadowSelect } from './shadowSelect'; +import $ from 'jquery'; + +/** + * + * ## About + * + * Cypress does not have native support for `shadowDom`. + * This module gives us the ability to run similar commands in shadow DOM. + * These commands are not as fully developed as the native ones, + * but resemble native Cypress commands in usage. + * + * > Only use these commands on elements *within* a `shadowRoot`, + * not on elements which may have a `shadowRoot`. + * + * What to Expect + * * Commands should feel familiar as Cypress ones and behave in similar ways + * * There is automatic retrying for certain commands (e.g. `shadowGet` and `shadowShould`) + * * Non-Dom results can be yielded into regular Cypress commands. + * For example: + * +```js +cy.shadowGet('some-shadow-element') + .then($el => $el.text()) + .should('match', /*.?/g); // no need to build non-DOM interactors! +``` + * + * The main differences are + * * Limited API use / less supported features + * * Retrying is on a per command (not per chain) basis (except for `shadowGet`, + * which does support retrying upcoming assertions) + * * No extra visibility/attachment/covered/disabled checks on `click` and `trigger` + * * Potentially others...TBD + * + ## API + * + * + * @module shadowDom + */ + +/** + * Register shadow commands on the `cy` namespace, + * such as `shadowGet`, `shadowShould`, etc. + */ +function registerShadowCommands() { + const OPTIONAL_SUBECT = { prevSubject: 'optional' }; + const PREV_SUBJECT = { prevSubject: true }; + + + Cypress.Commands.add('shadowGet', OPTIONAL_SUBECT, shadowGet); + + Cypress.Commands.add('shadowFind', PREV_SUBJECT, shadowFind); + + Cypress.Commands.add('shadowEq', PREV_SUBJECT, shadowEq); + + Cypress.Commands.add('shadowContains', PREV_SUBJECT, shadowContains); + + Cypress.Commands.add('shadowShould', PREV_SUBJECT, shadowShould); + + Cypress.Commands.add('shadowClick', PREV_SUBJECT, shadowClick); + + Cypress.Commands.add('shadowSelect', PREV_SUBJECT, shadowSelect); + + Cypress.Commands.add('shadowTrigger', PREV_SUBJECT, shadowTrigger); +} + +/** + * **Retryable / Async** + * + * * deeply searches DOM for all elements which have a `shadowRoot` + * * queries (with jquery) each `shadowRoot` with provided selector + * * Returns all matching results + * * Subject is optional + * + * @param {string} selector jquery selector used to find matching elements + * @param {object} [options] the options to modify behaviour of the command + * @param {number} [options.timeout] number of ms to retry the query until + * marking the command as failed. Defaults to 4s or the `defaultCommandTimeout` + * in cypress.config. + * @example +### Syntax + +```js +.shadowGet(selector) +.shadowGet(selector, options) +``` + +### Usage + +```js +// searches entire DOM +cy.shadowGet('custom-el-in-shadow') + +// searches only within container-el +cy.get('container-el') + .shadowGet('custom-el-in-shadow') + +// waits up to 30s (resolve immediately when found) +cy.shadowGet('custom-el-in-shadow', { timeout: 30000 }) + +``` + */ +function shadowGet(subject, selector, passedOptions = {}) { + smartlog.log('[shadowGet] arguments', subject, selector); + + const options = _addElemTimeoutMsg(selector, passedOptions); + // this._timeout = options.timeout || Cypress.config('defaultCommandTimeout') || 4000; + + // setup log to start spinner off + const log = Cypress.log({ + message: selector, + verify: true + }); + + let foundElements; + + const getElements = () => waitsFor(() => { + const win = cy.state('window'); + foundElements = _queryShadowChildren(subject ? subject[0] : win.document.body, selector); + return foundElements.length > 0; + }, options).then(() => { + smartlog.log('[shadowGet] found', foundElements); + // setting found $el on log to mimic cypress logging + log.$el = foundElements; + return $(foundElements); + }) + .catch((err) => { + // catching and rethrowing this way => + // error message applied to current log + Cypress.utils.throwErr(err, { + onFail: log + }) + }); + const resolveElements = function () { + cy.clearTimeout('shadowGet'); + return Promise["try"](getElements).then(function ($el) { + if (options.verify === false) { + return $el; + } + return cy.verifyUpcomingAssertions($el, options, { + onRetry: resolveElements + }); + }); + }; + + return resolveElements(); +} + +/** + * **Retryable / Async** + * + * * must be chained from another command (e.g. `shadowGet` or `get`) + * * queries (via jquery) the yielded element and the yielded element's `shadowRoot` for matches + * * **Only** searches within the `shadowRoot` of the yielded element (as well as just the regular DOM children) + * * **Note** it is a **shallow** search within the yielded elements shadowRoot. It will **not** + * do a deep search through shadowRoots for the matching element. For deep search, use `shadowGet` + * + * > You may wonder why a shallow search is needed. That's because in shadowDom + * > Unique selectors like `id`s can be repeated. Sometimes we just want to search + * > in the immediate root without worrying about coliding with things further down + * > the DOM tree. + * + * @param {string} selector jquery selector used to find matching elements + * @param {object} [options] the options to modify behaviour of the command + * @param {number} [options.timeout] number of ms to retry the query until + * marking the command as failed. Defaults to 4s or the `defaultCommandTimeout` + * in cypress.config. + * @example +### Syntax + +```js +.shadowFind(selector) +.shadowFind(selector, options) +``` + +### Usage + +```js +// shadowFind queries against the subjects' children and shadowRoot +cy.get('container-el') + .shadowFind('button.action') + +// shadowGet matches from all shadowRoots +// shadowFind queries against the subjects' children and shadowRoot +cy.shadowGet('custom-el-in-shadow') + .shadowFind('button.action') // will query against the subject's children and shadowRoot +``` + */ +function shadowFind(subject, selector, passedOptions) { + smartlog.log('[shadowFind] arguments', subject, selector); + + const options = _addElemTimeoutMsg(selector, passedOptions); + + // setup log to start spinner off + const log = Cypress.log({ + message: selector + }); + + let foundElements; + + // returning promise => spinner + return waitsFor(() => { + foundElements = _queryChildrenAndShadowRoot(subject, selector); + return foundElements.length > 0; + }, options).then(() => { + smartlog.log('[shadowFind] found', foundElements); + // setting found $el on log to mimic cypress logging + log.$el = foundElements; + return foundElements; + }) + .catch((err) => { + // catching and rethrowing this way => + // error message applied to current log + Cypress.utils.throwErr(err, { + onFail: log + }) + }); +} + +/** + * **Retryable / Async** + * (Up to 4s, timeout not customizable) + * + * This Utility is most useful when needing to run + * assertions against shadowDom elements and it does so by leveraging `jquery-chai`. `Cypress` also does this, but it does not work in shadowDom. + * * it accepts the `string` syntax like Cypress' `should` + * * it does not accept the `function` syntax + * (but you can still use `should` with shadow elements as long as you run non `jquery-chai` assertions) + * * This smooths over the issues with Cypress' `jquery-chai`, + * which does explicit checks that are incompatible with shadowDom. + * * It uses a clean version of jquery and chai to run assertions against shadowDom elements. + * * In general, you can use `should` as long as you do not need to assert against the shadow DOM. + * + * > **When should I use `shadowShould` and when should I use `should`?** + * > + * > Use `shadowShould` whenever you need to run assertions + * against elements within the `shadowDom`. + * > Lite DOM and regular DOM can be used with `should`. + * > Also, any non-DOM values can be used with `should`. + * > You can do something like, + * > + * >`.should(($el) => expect($el.text()).to.match(/.?/))` + * > + * > Or even, + * > + * > `.then(($el) => $el.text()).should('match', /.?/))`. + * > + * > These are examples of taking non-DOM values from the shadowDom elements and using + * > regular Cypress commands and assertions on them. + * + * @param {string} chainer the string + * @param {any} value the value to be checked + * @param {string} method the sub-chainer to check (see example) + * + * @example +### Syntax + +```js +.shadowShould(chainers) +.shadowShould(chainers, value) +.shadowShould(chainers, method, value) +``` + +### Usage + +```js +cy.get('@dateLabel') + .should('have.text', '2017-11-22'); + +cy.get('@datepicker') + .shadowFind('button[value="2017-11-22"]') + .shadowShould('have.attr', 'tabindex', '0'); +``` + + */ + +function shadowShould(subject, chainer, ...rest) { + const message = _formatElementMessage(subject[0], chainer, ...rest); + + const log = Cypress.log({ + message: message, + state: 'pending' + }); + + // allow jquery chai to throw an error if we pass a bad chainer + ensureChainerExists(subject, chainer); + + return waitsFor(() => { + try { + should(subject, chainer, ...rest); + return true; + } catch (ex) { + return false; + } + }) + .then(() => { + assert(true, message); + return subject; + }) + .catch(() => { + try { + should(subject, chainer, ...rest); + } catch (err) { + Cypress.utils.throwErr(err, { + onFail: log + }) + } + }); +} + +/** + * **No-Retry / Sync** + * + * Yields a subject at the index from a given subject. + * + * For example, if this is chained from a `shadowGet` + * which yields multiple elements, `shadowEq` will return + * the element at the specified index. + * + * It will *not* retry and yields whatever is passed + * into it synchronously. + * + * @param {number} index specifies the index of + * the element to yield from the subject + * + * @example +### Syntax + +```js +.shadowEq(selector) +``` + +### Usage + +```js +cy.get('container-el') + .shadowFind('button.action') + .shadowEq(2) + +cy.shadowGet('custom-el-in-shadow') + .shadowEq(4) +``` + */ +function shadowEq(subject, index) { + Cypress.log({ + name: 'shadoweq', + message: index, + }); + + // todo check into how cypress handles this situation + if (subject[index] === undefined) { + throw new Error('There is no subject at the specified index'); + } + + return subject[index]; +} + +/** + * **No-Retry / Sync** + * + * * Allows you to click on an element within a shadowRoot. + * * Can be chained from `shadowGet`, `shadowFind`, or `shadowEq` + * * Clicks on the first element (index 0) from the + * yielded elements of previous command + * * Cypress' `click` does not work in shadowDom for multiple reasons + * * Uses native or jquery .click functionality, + * but does not do additional checks Cypress' click does + * such as checking the component is visible, + * not covered, and not disabled. + * * Would need to put in more work to ensure component clicks cannot pass + * through when the component is not in an actual interactive state. + * @example +### Syntax + +```js +.shadowClick() +``` + +### Usage + +```js +cy.get('container-el') + .shadowGet('custom-element-within-shadow-dom') + .shadowFind('button.action') + .shadowClick() +``` + */ +function shadowClick(subject, options) { + Cypress.log({ + name: 'shadowclick' + }); + // todo: retry until element is "clickable" + if (subject[0].tagName === 'A') { + // jQuery click doesn't work on Anchors + // https://stackoverflow.com/questions/34174134/triggering-click-event-on-anchor-tag-doesnt-works + subject[0].click() + } else { + subject.click(); + } +} + +/** + * **No-Retry / Sync** + * + * * Allows you to select an option from a `select` element within a shadowRoot. + * * Can be chained from `shadowGet`, `shadowFind`, or `shadowEq` + * * Expects an actual `select` element to be the subject + * * Selects the provided `option` from the first element (index 0) from the + * yielded elements of previous command + * * Option can be by `value` or by `text`, but must be strictly equal + * * Cypress' `select` does not work in shadowDom for multiple reasons + * * Does not do additional checks Cypress' select does + * such as checking the component is visible, + * not covered, and not disabled. + * * Would need to put in more work to ensure component selects cannot pass + * through when the component is not in an actual interactive state. + * + * + * @param {String|Number} option The option from the `select` to select, + * by `value` or by `text` + * + * @example +### Syntax + +```js +.shadowSelect(option) +``` + +### Usage + +```js + cy + .shadowGet('upd-select[name="state"]') + .shadowFind('select') + .shadowSelect('AL'); // by value + + cy + .shadowGet('upd-select[name="bedrooms"]') + .shadowFind('select') + .shadowSelect('3 Bedroooms'); // by text +``` + */ +function shadowSelect(subject, select, options) { + + Cypress.log({ + name: 'shadowselect', + message: `'${select}' on ${subject[0].tagName}`, + }); + + return _shadowSelect(subject, select, options); +} + +/** + * **No Retry / Sync** + * + * * allows to trigger an event similarly to how Cypress' `trigger` works. + * * This works with elements on the shadow DOM since they pose problems with + * almost all of Cypress' commands. + * * Currently only supports these events: + * * `keydown` + * * `keypress` + * * `keyup` + * * `change` + * * `input` + * * Options can also be provided per event + * * `key` events supports keyboard event options i.e. `keyCode` or `bubbles` + * * `change` and `input` events support the `value` for the update + * + * @param {string} event The event from the above list which will be triggered + * @param {object|value} options Options which depend on the kind of event and + * modify the event's behaviour + * + * @example +```js +.shadowTrigger(event) +.shadowTrigger(event, options) +``` + +### Usage + +```js +// Changing an input +cy + .shadowGet('upd-input[name="postalCode"]') + .shadowFind('input') + .shadowTrigger('input', '99511'); + +// changing value of a custom element +cy + .shadowGet('upd-datepicker') + .shadowTrigger('change', '2019-01-02'); + +// triggering a key event +cy.get('@datepicker') + .shadowFind('button[aria-selected="true"]') + .shadowTrigger('keydown', { keyCode: 39, bubbles: true }); +``` + */ +function shadowTrigger(subject, event, options) { + Cypress.log({ + name: 'shadowTrigger', + }); + // todo: retry until element is "interactable" + const createdEvent = _createEvent(event, options, subject[0]); + console.log(createdEvent); + if (!createdEvent) { + throw new Error(`Event not supported by shadowTrigger: ${event}`); + } + subject[0].dispatchEvent(createdEvent); + return subject; +} + +/** + * **No-Retry / Sync** + * + * Convenience function to assert partial match between the `textContent` of + * an element and the passed in value. + * + * This does not work like `cy.contains`. + * + * Literally just runs this assertion: + * ```js + * expect(subject[0].textContent).to.contain(text) + * ``` + * + * @param {string} text the text to match against + * + * @example +```js +.shadowContains(text) +``` + +### Usage + +```js + cy + .shadowGet('some-custom-elem') + .shadowContains('Should contain this text...') +``` + */ +function shadowContains(subject, text) { + expect(subject[0].textContent).to.contain(text) + return subject; +} + +/* PRIVATE / INTERNAL */ + +function _formatElementMessage(element, chainer, ...rest) { + const elementText = element.tagName.toLowerCase(), + id = element.id ? `#${element.id}` : '', + chainerText = chainer.replace('.', ' '), + check = rest.join(' '); + return `expect **<${elementText}${id}>** to ${chainerText} **${check}**`; +} + +function _isShadowElement(el) { + return ( + el.shadowRoot && + el.shadowRoot.childNodes && + el.shadowRoot.childNodes.length > 0 + ); +} + +function _getAllShadowChildren(elems) { + return elems.reduce((acc, el) => { + acc.push(el); + let nodesToReduce = []; + // smartlog.log('[shadow] get for element', el); + if (el.childNodes) { + nodesToReduce = nodesToReduce.concat([...el.childNodes]); + } + if (el.shadowRoot) { + nodesToReduce = nodesToReduce.concat([...el.shadowRoot.childNodes]); + } + if (el.tagName === 'SLOT') { + // NOTE: `assignedNodes` are part of litedom, therefore will be in the childNodes + // doing this here duplicates what we return + // nodesToReduce = nodesToReduce.concat([...el.assignedNodes()]) + } + return acc.concat(_getAllShadowChildren(nodesToReduce)); + }, []) +} + +function _queryShadowChildren(node, query) { + const shadowRoots = _getAllShadowChildren([node]).filter(_isShadowElement); + + smartlog.log('[shadowroots]', shadowRoots); + const matchedEls = shadowRoots.reduce( + (matched, el) => [...matched, ...$(el.shadowRoot).find(query)], + [] + ); + + smartlog.log('Matched els', matchedEls); + + return matchedEls; +} + +function _queryChildrenAndShadowRoot(subject, selector) { + let foundElements = [...subject.find(selector)]; + const shadowRoots = [...subject].map(s => s.shadowRoot).filter(root => root !== undefined); + shadowRoots.forEach((root) => { + foundElements = [...$(root).find(selector)].concat(foundElements); + }); + return foundElements; +} + +function _addElemTimeoutMsg(selector, opts) { + return merge({}, opts, { + message: `Expected to find element: '${selector}' but never found it.`, + }); +} + +function _createEvent(event, options, element) { + if (['keydown', 'keypress', 'keyup'].indexOf(event) !== -1) { + // In Chromium/Electron, dispatchEvent with keyCode will be received as 0 + // this fixes that issue + // https://stackoverflow.com/questions/10455626/keydown-simulation-in-chrome-fires-normally-but-not-the-correct-key/10520017#10520017 + const customEvent = document.createEvent('KeyboardEvent', options), + { keyCode } = options; + + ['keyCode', 'which'].forEach(prop => { + Object.defineProperty(customEvent, prop, { + get: function () { + return this.keyCodeVal; + }, + }); + }); + + if (customEvent.initKeyboardEvent) { + customEvent.initKeyboardEvent( + event, + true, + true, + document.defaultView, + false, + false, + false, + false, + keyCode, + keyCode + ); + } else { + customEvent.initKeyEvent( + event, + true, + true, + document.defaultView, + false, + false, + false, + false, + keyCode, + 0 + ); + } + + customEvent.keyCodeVal = keyCode; + + if (customEvent.keyCode !== keyCode) { + throw new Error( + `keyCode mismatch keyCode ${customEvent.keyCode} which ${ + customEvent.which + }` + ); + } + + return customEvent; + } + if (['change', 'input'].indexOf(event) !== -1) { + element.value = options; + const change = document.createEvent("HTMLEvents"); + change.initEvent(event, true, true); + return change; + } +} + +export { registerShadowCommands }; diff --git a/bower_components/existdb-usermanager/cypress/support/shadowDom_spec.js b/bower_components/existdb-usermanager/cypress/support/shadowDom_spec.js new file mode 100644 index 00000000..b787dd1f --- /dev/null +++ b/bower_components/existdb-usermanager/cypress/support/shadowDom_spec.js @@ -0,0 +1,100 @@ +import $ from 'jquery'; + +const createShadowDom = (host, dom) => { + const shadow = host.attachShadow({ mode: 'open' }); + shadow.innerHTML = dom; +} + +const final = 5; + +const createOrderTest = (host, count) => { + const elem = host.shadowRoot.querySelector('.order-test'); + count++; + createShadowDom(elem, `
`); + if (count < final) { + createOrderTest(elem, count); + } +} + + +describe('shadowDom', () => { + before(() => { + cy.visit('/fixtures/index.html'); + cy.get('cypress-spec') + .then($el => { + createShadowDom($el.get(0), ` +
Test Div + 1 + 2 +
+ +
+
+
+
+ `) + createOrderTest($el.get(0), 0); + }) + }) + context('shadowCommands', () => { + it('in general these commands work. this is good enough for now.', () => { + cy.shadowGet('#test') + .shadowFind('span') + .shadowShould('have.length', 2) + .shadowEq(1) + .should($el => { + assert($el, 'shadowGet is able to retrieve the shadow element'); + assert($el, 'shadowFind span is able to get the elements'); + assert($el, 'shadowShould was able to make the assertion'); + }) + }); + + it('shadowGet returns elements in correct order', () => { + [...Array(6)].forEach((_, i) => { + cy.shadowGet('.order-test') + .shadowEq(i) + .shadowShould('have.class', i) + }) + }) + }); + + context('retryability', () => { + it('retries the starter element until all chained commands pass', () => { + let count = 0; + + // add another option into the select after 2 seconds + // to ensure that the shadowGet will not resolve immediately + // with the correct state + cy.shadowGet('select option') + .then($el => { + setTimeout(() => { + $el.parent().append('