diff --git a/app/client/cypress.json b/app/client/cypress.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/app/client/cypress.json @@ -0,0 +1 @@ +{} diff --git a/app/client/cypress/fixtures/example.json b/app/client/cypress/fixtures/example.json new file mode 100644 index 0000000000..da18d9352a --- /dev/null +++ b/app/client/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/app/client/cypress/integration/examples/actions.spec.js b/app/client/cypress/integration/examples/actions.spec.js new file mode 100644 index 0000000000..03eba5e333 --- /dev/null +++ b/app/client/cypress/integration/examples/actions.spec.js @@ -0,0 +1,309 @@ +/// + +context("Actions", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/actions"); + }); + + // https://on.cypress.io/interacting-with-elements + + it(".type() - type into a DOM element", () => { + // https://on.cypress.io/type + cy.get(".action-email") + .type("fake@email.com") + .should("have.value", "fake@email.com") + + // .type() with special character sequences + .type("{leftarrow}{rightarrow}{uparrow}{downarrow}") + .type("{del}{selectall}{backspace}") + + // .type() with key modifiers + .type("{alt}{option}") //these are equivalent + .type("{ctrl}{control}") //these are equivalent + .type("{meta}{command}{cmd}") //these are equivalent + .type("{shift}") + + // Delay each keypress by 0.1 sec + .type("slow.typing@email.com", { delay: 100 }) + .should("have.value", "slow.typing@email.com"); + + cy.get(".action-disabled") + // Ignore error checking prior to type + // like whether the input is visible or disabled + .type("disabled error checking", { force: true }) + .should("have.value", "disabled error checking"); + }); + + it(".focus() - focus on a DOM element", () => { + // https://on.cypress.io/focus + cy.get(".action-focus") + .focus() + .should("have.class", "focus") + .prev() + .should("have.attr", "style", "color: orange;"); + }); + + it(".blur() - blur off a DOM element", () => { + // https://on.cypress.io/blur + cy.get(".action-blur") + .type("About to blur") + .blur() + .should("have.class", "error") + .prev() + .should("have.attr", "style", "color: red;"); + }); + + it(".clear() - clears an input or textarea element", () => { + // https://on.cypress.io/clear + cy.get(".action-clear") + .type("Clear this text") + .should("have.value", "Clear this text") + .clear() + .should("have.value", ""); + }); + + it(".submit() - submit a form", () => { + // https://on.cypress.io/submit + cy.get(".action-form") + .find('[type="text"]') + .type("HALFOFF"); + cy.get(".action-form") + .submit() + .next() + .should("contain", "Your form has been submitted!"); + }); + + it(".click() - click on a DOM element", () => { + // https://on.cypress.io/click + cy.get(".action-btn").click(); + + // You can click on 9 specific positions of an element: + // ----------------------------------- + // | topLeft top topRight | + // | | + // | | + // | | + // | left center right | + // | | + // | | + // | | + // | bottomLeft bottom bottomRight | + // ----------------------------------- + + // clicking in the center of the element is the default + cy.get("#action-canvas").click(); + + cy.get("#action-canvas").click("topLeft"); + cy.get("#action-canvas").click("top"); + cy.get("#action-canvas").click("topRight"); + cy.get("#action-canvas").click("left"); + cy.get("#action-canvas").click("right"); + cy.get("#action-canvas").click("bottomLeft"); + cy.get("#action-canvas").click("bottom"); + cy.get("#action-canvas").click("bottomRight"); + + // .click() accepts an x and y coordinate + // that controls where the click occurs :) + + cy.get("#action-canvas") + .click(80, 75) // click 80px on x coord and 75px on y coord + .click(170, 75) + .click(80, 165) + .click(100, 185) + .click(125, 190) + .click(150, 185) + .click(170, 165); + + // click multiple elements by passing multiple: true + cy.get(".action-labels>.label").click({ multiple: true }); + + // Ignore error checking prior to clicking + cy.get(".action-opacity>.btn").click({ force: true }); + }); + + it(".dblclick() - double click on a DOM element", () => { + // https://on.cypress.io/dblclick + + // Our app has a listener on 'dblclick' event in our 'scripts.js' + // that hides the div and shows an input on double click + cy.get(".action-div") + .dblclick() + .should("not.be.visible"); + cy.get(".action-input-hidden").should("be.visible"); + }); + + it(".rightclick() - right click on a DOM element", () => { + // https://on.cypress.io/rightclick + + // Our app has a listener on 'contextmenu' event in our 'scripts.js' + // that hides the div and shows an input on right click + cy.get(".rightclick-action-div") + .rightclick() + .should("not.be.visible"); + cy.get(".rightclick-action-input-hidden").should("be.visible"); + }); + + it(".check() - check a checkbox or radio element", () => { + // https://on.cypress.io/check + + // By default, .check() will check all + // matching checkbox or radio elements in succession, one after another + cy.get('.action-checkboxes [type="checkbox"]') + .not("[disabled]") + .check() + .should("be.checked"); + + cy.get('.action-radios [type="radio"]') + .not("[disabled]") + .check() + .should("be.checked"); + + // .check() accepts a value argument + cy.get('.action-radios [type="radio"]') + .check("radio1") + .should("be.checked"); + + // .check() accepts an array of values + cy.get('.action-multiple-checkboxes [type="checkbox"]') + .check(["checkbox1", "checkbox2"]) + .should("be.checked"); + + // Ignore error checking prior to checking + cy.get(".action-checkboxes [disabled]") + .check({ force: true }) + .should("be.checked"); + + cy.get('.action-radios [type="radio"]') + .check("radio3", { force: true }) + .should("be.checked"); + }); + + it(".uncheck() - uncheck a checkbox element", () => { + // https://on.cypress.io/uncheck + + // By default, .uncheck() will uncheck all matching + // checkbox elements in succession, one after another + cy.get('.action-check [type="checkbox"]') + .not("[disabled]") + .uncheck() + .should("not.be.checked"); + + // .uncheck() accepts a value argument + cy.get('.action-check [type="checkbox"]') + .check("checkbox1") + .uncheck("checkbox1") + .should("not.be.checked"); + + // .uncheck() accepts an array of values + cy.get('.action-check [type="checkbox"]') + .check(["checkbox1", "checkbox3"]) + .uncheck(["checkbox1", "checkbox3"]) + .should("not.be.checked"); + + // Ignore error checking prior to unchecking + cy.get(".action-check [disabled]") + .uncheck({ force: true }) + .should("not.be.checked"); + }); + + it(".select() - select an option in a element", () => { + // https://on.cypress.io/select + + // Select option(s) with matching text content + cy.get(".action-select").select("apples"); + + cy.get(".action-select-multiple").select(["apples", "oranges", "bananas"]); + + // Select option(s) with matching value + cy.get(".action-select").select("fr-bananas"); + + cy.get(".action-select-multiple").select([ + "fr-apples", + "fr-oranges", + "fr-bananas", + ]); + }); + + it(".scrollIntoView() - scroll an element into view", () => { + // https://on.cypress.io/scrollintoview + + // normally all of these buttons are hidden, + // because they're not within + // the viewable area of their parent + // (we need to scroll to see them) + cy.get("#scroll-horizontal button").should("not.be.visible"); + + // scroll the button into view, as if the user had scrolled + cy.get("#scroll-horizontal button") + .scrollIntoView() + .should("be.visible"); + + cy.get("#scroll-vertical button").should("not.be.visible"); + + // Cypress handles the scroll direction needed + cy.get("#scroll-vertical button") + .scrollIntoView() + .should("be.visible"); + + cy.get("#scroll-both button").should("not.be.visible"); + + // Cypress knows to scroll to the right and down + cy.get("#scroll-both button") + .scrollIntoView() + .should("be.visible"); + }); + + it(".trigger() - trigger an event on a DOM element", () => { + // https://on.cypress.io/trigger + + // To interact with a range input (slider) + // we need to set its value & trigger the + // event to signal it changed + + // Here, we invoke jQuery's val() method to set + // the value and trigger the 'change' event + cy.get(".trigger-input-range") + .invoke("val", 25) + .trigger("change") + .get("input[type=range]") + .siblings("p") + .should("have.text", "25"); + }); + + it("cy.scrollTo() - scroll the window or element to a position", () => { + // https://on.cypress.io/scrollTo + + // You can scroll to 9 specific positions of an element: + // ----------------------------------- + // | topLeft top topRight | + // | | + // | | + // | | + // | left center right | + // | | + // | | + // | | + // | bottomLeft bottom bottomRight | + // ----------------------------------- + + // if you chain .scrollTo() off of cy, we will + // scroll the entire window + cy.scrollTo("bottom"); + + cy.get("#scrollable-horizontal").scrollTo("right"); + + // or you can scroll to a specific coordinate: + // (x axis, y axis) in pixels + cy.get("#scrollable-vertical").scrollTo(250, 250); + + // or you can scroll to a specific percentage + // of the (width, height) of the element + cy.get("#scrollable-both").scrollTo("75%", "25%"); + + // control the easing of the scroll (default is 'swing') + cy.get("#scrollable-vertical").scrollTo("center", { easing: "linear" }); + + // control the duration of the scroll (in ms) + cy.get("#scrollable-both").scrollTo("center", { duration: 2000 }); + }); +}); diff --git a/app/client/cypress/integration/examples/aliasing.spec.js b/app/client/cypress/integration/examples/aliasing.spec.js new file mode 100644 index 0000000000..9421d628c1 --- /dev/null +++ b/app/client/cypress/integration/examples/aliasing.spec.js @@ -0,0 +1,46 @@ +/// + +context("Aliasing", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/aliasing"); + }); + + it(".as() - alias a DOM element for later use", () => { + // https://on.cypress.io/as + + // Alias a DOM element for use later + // We don't have to traverse to the element + // later in our code, we reference it with @ + + cy.get(".as-table") + .find("tbody>tr") + .first() + .find("td") + .first() + .find("button") + .as("firstBtn"); + + // when we reference the alias, we place an + // @ in front of its name + cy.get("@firstBtn").click(); + + cy.get("@firstBtn") + .should("have.class", "btn-success") + .and("contain", "Changed"); + }); + + it(".as() - alias a route for later use", () => { + // Alias the route to wait for its response + cy.server(); + cy.route("GET", "comments/*").as("getComment"); + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get(".network-btn").click(); + + // https://on.cypress.io/wait + cy.wait("@getComment") + .its("status") + .should("eq", 200); + }); +}); diff --git a/app/client/cypress/integration/examples/assertions.spec.js b/app/client/cypress/integration/examples/assertions.spec.js new file mode 100644 index 0000000000..85541b4d5b --- /dev/null +++ b/app/client/cypress/integration/examples/assertions.spec.js @@ -0,0 +1,168 @@ +/// + +context("Assertions", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/assertions"); + }); + + describe("Implicit Assertions", () => { + it(".should() - make an assertion about the current subject", () => { + // https://on.cypress.io/should + cy.get(".assertion-table") + .find("tbody tr:last") + .should("have.class", "success") + .find("td") + .first() + // checking the text of the element in various ways + .should("have.text", "Column content") + .should("contain", "Column content") + .should("have.html", "Column content") + // chai-jquery uses "is()" to check if element matches selector + .should("match", "td") + // to match text content against a regular expression + // first need to invoke jQuery method text() + // and then match using regular expression + .invoke("text") + .should("match", /column content/i); + + // a better way to check element's text content against a regular expression + // is to use "cy.contains" + // https://on.cypress.io/contains + cy.get(".assertion-table") + .find("tbody tr:last") + // finds first element with text content matching regular expression + .contains("td", /column content/i) + .should("be.visible"); + + // for more information about asserting element's text + // see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents + }); + + it(".and() - chain multiple assertions together", () => { + // https://on.cypress.io/and + cy.get(".assertions-link") + .should("have.class", "active") + .and("have.attr", "href") + .and("include", "cypress.io"); + }); + }); + + describe("Explicit Assertions", () => { + // https://on.cypress.io/assertions + it("expect - make an assertion about a specified subject", () => { + // We can use Chai's BDD style assertions + expect(true).to.be.true; + const o = { foo: "bar" }; + + expect(o).to.equal(o); + expect(o).to.deep.equal({ foo: "bar" }); + // matching text using regular expression + expect("FooBar").to.match(/bar$/i); + }); + + it("pass your own callback function to should()", () => { + // Pass a function to should that can have any number + // of explicit assertions within it. + // The ".should(cb)" function will be retried + // automatically until it passes all your explicit assertions or times out. + cy.get(".assertions-p") + .find("p") + .should($p => { + // https://on.cypress.io/$ + // return an array of texts from all of the p's + // @ts-ignore TS6133 unused variable + const texts = $p.map((i, el) => Cypress.$(el).text()); + + // jquery map returns jquery object + // and .get() convert this to simple array + const paragraphs = texts.get(); + + // array should have length of 3 + expect(paragraphs, "has 3 paragraphs").to.have.length(3); + + // use second argument to expect(...) to provide clear + // message with each assertion + expect(paragraphs, "has expected text in each paragraph").to.deep.eq([ + "Some text from first p", + "More text from second p", + "And even more text from third p", + ]); + }); + }); + + it("finds element by class name regex", () => { + cy.get(".docs-header") + .find("div") + // .should(cb) callback function will be retried + .should($div => { + expect($div).to.have.length(1); + + const className = $div[0].className; + + expect(className).to.match(/heading-/); + }) + // .then(cb) callback is not retried, + // it either passes or fails + .then($div => { + expect($div, "text content").to.have.text("Introduction"); + }); + }); + + it("can throw any error", () => { + cy.get(".docs-header") + .find("div") + .should($div => { + if ($div.length !== 1) { + // you can throw your own errors + throw new Error("Did not find 1 element"); + } + + const className = $div[0].className; + + if (!className.match(/heading-/)) { + throw new Error(`Could not find class "heading-" in ${className}`); + } + }); + }); + + it("matches unknown text between two elements", () => { + /** + * Text from the first element. + * @type {string} + */ + let text; + + /** + * Normalizes passed text, + * useful before comparing text with spaces and different capitalization. + * @param {string} s Text to normalize + */ + const normalizeText = s => s.replace(/\s/g, "").toLowerCase(); + + cy.get(".two-elements") + .find(".first") + .then($first => { + // save text from the first element + text = normalizeText($first.text()); + }); + + cy.get(".two-elements") + .find(".second") + .should($div => { + // we can massage text before comparing + const secondText = normalizeText($div.text()); + + expect(secondText, "second text").to.equal(text); + }); + }); + + it("assert - assert shape of an object", () => { + const person = { + name: "Joe", + age: 20, + }; + + assert.isObject(person, "value is object"); + }); + }); +}); diff --git a/app/client/cypress/integration/examples/connectors.spec.js b/app/client/cypress/integration/examples/connectors.spec.js new file mode 100644 index 0000000000..4b7a7699fd --- /dev/null +++ b/app/client/cypress/integration/examples/connectors.spec.js @@ -0,0 +1,96 @@ +/// + +context("Connectors", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/connectors"); + }); + + it(".each() - iterate over an array of elements", () => { + // https://on.cypress.io/each + cy.get(".connectors-each-ul>li").each(($el, index, $list) => { + console.log($el, index, $list); + }); + }); + + it(".its() - get properties on the current subject", () => { + // https://on.cypress.io/its + cy.get(".connectors-its-ul>li") + // calls the 'length' property yielding that value + .its("length") + .should("be.gt", 2); + }); + + it(".invoke() - invoke a function on the current subject", () => { + // our div is hidden in our script.js + // $('.connectors-div').hide() + + // https://on.cypress.io/invoke + cy.get(".connectors-div") + .should("be.hidden") + // call the jquery method 'show' on the 'div.container' + .invoke("show") + .should("be.visible"); + }); + + it(".spread() - spread an array as individual args to callback function", () => { + // https://on.cypress.io/spread + const arr = ["foo", "bar", "baz"]; + + cy.wrap(arr).spread((foo, bar, baz) => { + expect(foo).to.eq("foo"); + expect(bar).to.eq("bar"); + expect(baz).to.eq("baz"); + }); + }); + + describe(".then()", () => { + it("invokes a callback function with the current subject", () => { + // https://on.cypress.io/then + cy.get(".connectors-list > li").then($lis => { + expect($lis, "3 items").to.have.length(3); + expect($lis.eq(0), "first item").to.contain("Walk the dog"); + expect($lis.eq(1), "second item").to.contain("Feed the cat"); + expect($lis.eq(2), "third item").to.contain("Write JavaScript"); + }); + }); + + it("yields the returned value to the next command", () => { + cy.wrap(1) + .then(num => { + expect(num).to.equal(1); + + return 2; + }) + .then(num => { + expect(num).to.equal(2); + }); + }); + + it("yields the original subject without return", () => { + cy.wrap(1) + .then(num => { + expect(num).to.equal(1); + // note that nothing is returned from this callback + }) + .then(num => { + // this callback receives the original unchanged value 1 + expect(num).to.equal(1); + }); + + it("yields the value yielded by the last Cypress command inside", () => { + cy.wrap(1) + .then(num => { + expect(num).to.equal(1); + // note how we run a Cypress command + // the result yielded by this Cypress command + // will be passed to the second ".then" + cy.wrap(2); + }) + .then(num => { + // this callback receives the value yielded by "cy.wrap(2)" + expect(num).to.equal(2); + }); + }); + }); + }); +}); diff --git a/app/client/cypress/integration/examples/cookies.spec.js b/app/client/cypress/integration/examples/cookies.spec.js new file mode 100644 index 0000000000..ce35df8f88 --- /dev/null +++ b/app/client/cypress/integration/examples/cookies.spec.js @@ -0,0 +1,79 @@ +/// + +context("Cookies", () => { + beforeEach(() => { + Cypress.Cookies.debug(true); + + cy.visit("https://example.cypress.io/commands/cookies"); + + // clear cookies again after visiting to remove + // any 3rd party cookies picked up such as cloudflare + cy.clearCookies(); + }); + + it("cy.getCookie() - get a browser cookie", () => { + // https://on.cypress.io/getcookie + cy.get("#getCookie .set-a-cookie").click(); + + // cy.getCookie() yields a cookie object + cy.getCookie("token").should("have.property", "value", "123ABC"); + }); + + it("cy.getCookies() - get browser cookies", () => { + // https://on.cypress.io/getcookies + cy.getCookies().should("be.empty"); + + cy.get("#getCookies .set-a-cookie").click(); + + // cy.getCookies() yields an array of cookies + cy.getCookies() + .should("have.length", 1) + .should(cookies => { + // each cookie has these properties + expect(cookies[0]).to.have.property("name", "token"); + expect(cookies[0]).to.have.property("value", "123ABC"); + expect(cookies[0]).to.have.property("httpOnly", false); + expect(cookies[0]).to.have.property("secure", false); + expect(cookies[0]).to.have.property("domain"); + expect(cookies[0]).to.have.property("path"); + }); + }); + + it("cy.setCookie() - set a browser cookie", () => { + // https://on.cypress.io/setcookie + cy.getCookies().should("be.empty"); + + cy.setCookie("foo", "bar"); + + // cy.getCookie() yields a cookie object + cy.getCookie("foo").should("have.property", "value", "bar"); + }); + + it("cy.clearCookie() - clear a browser cookie", () => { + // https://on.cypress.io/clearcookie + cy.getCookie("token").should("be.null"); + + cy.get("#clearCookie .set-a-cookie").click(); + + cy.getCookie("token").should("have.property", "value", "123ABC"); + + // cy.clearCookies() yields null + cy.clearCookie("token").should("be.null"); + + cy.getCookie("token").should("be.null"); + }); + + it("cy.clearCookies() - clear browser cookies", () => { + // https://on.cypress.io/clearcookies + cy.getCookies().should("be.empty"); + + cy.get("#clearCookies .set-a-cookie").click(); + + cy.getCookies().should("have.length", 1); + + // cy.clearCookies() yields null + cy.clearCookies(); + + cy.getCookies().should("be.empty"); + }); +}); diff --git a/app/client/cypress/integration/examples/cypress_api.spec.js b/app/client/cypress/integration/examples/cypress_api.spec.js new file mode 100644 index 0000000000..54b1364ec3 --- /dev/null +++ b/app/client/cypress/integration/examples/cypress_api.spec.js @@ -0,0 +1,228 @@ +/// + +context("Cypress.Commands", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + // https://on.cypress.io/custom-commands + + it(".add() - create a custom command", () => { + Cypress.Commands.add( + "console", + { + prevSubject: true, + }, + (subject, method) => { + // the previous subject is automatically received + // and the commands arguments are shifted + + // allow us to change the console method used + method = method || "log"; + + // log the subject to the console + // @ts-ignore TS7017 + console[method]("The subject is", subject); + + // whatever we return becomes the new subject + // we don't want to change the subject so + // we return whatever was passed in + return subject; + }, + ); + + // @ts-ignore TS2339 + cy.get("button") + .console("info") + .then($button => { + // subject is still $button + }); + }); +}); + +context("Cypress.Cookies", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + // https://on.cypress.io/cookies + it(".debug() - enable or disable debugging", () => { + Cypress.Cookies.debug(true); + + // Cypress will now log in the console when + // cookies are set or cleared + cy.setCookie("fakeCookie", "123ABC"); + cy.clearCookie("fakeCookie"); + cy.setCookie("fakeCookie", "123ABC"); + cy.clearCookie("fakeCookie"); + cy.setCookie("fakeCookie", "123ABC"); + }); + + it(".preserveOnce() - preserve cookies by key", () => { + // normally cookies are reset after each test + cy.getCookie("fakeCookie").should("not.be.ok"); + + // preserving a cookie will not clear it when + // the next test starts + cy.setCookie("lastCookie", "789XYZ"); + Cypress.Cookies.preserveOnce("lastCookie"); + }); + + it(".defaults() - set defaults for all cookies", () => { + // now any cookie with the name 'session_id' will + // not be cleared before each new test runs + Cypress.Cookies.defaults({ + whitelist: "session_id", + }); + }); +}); + +context("Cypress.Server", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + // Permanently override server options for + // all instances of cy.server() + + // https://on.cypress.io/cypress-server + it(".defaults() - change default config of server", () => { + Cypress.Server.defaults({ + delay: 0, + force404: false, + }); + }); +}); + +context("Cypress.arch", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + it("Get CPU architecture name of underlying OS", () => { + // https://on.cypress.io/arch + expect(Cypress.arch).to.exist; + }); +}); + +context("Cypress.config()", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + it("Get and set configuration options", () => { + // https://on.cypress.io/config + const myConfig = Cypress.config(); + + expect(myConfig).to.have.property("animationDistanceThreshold", 5); + expect(myConfig).to.have.property("baseUrl", null); + expect(myConfig).to.have.property("defaultCommandTimeout", 4000); + expect(myConfig).to.have.property("requestTimeout", 5000); + expect(myConfig).to.have.property("responseTimeout", 30000); + expect(myConfig).to.have.property("viewportHeight", 660); + expect(myConfig).to.have.property("viewportWidth", 1000); + expect(myConfig).to.have.property("pageLoadTimeout", 60000); + expect(myConfig).to.have.property("waitForAnimations", true); + + expect(Cypress.config("pageLoadTimeout")).to.eq(60000); + + // this will change the config for the rest of your tests! + Cypress.config("pageLoadTimeout", 20000); + + expect(Cypress.config("pageLoadTimeout")).to.eq(20000); + + Cypress.config("pageLoadTimeout", 60000); + }); +}); + +context("Cypress.dom", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + // https://on.cypress.io/dom + it(".isHidden() - determine if a DOM element is hidden", () => { + const hiddenP = Cypress.$(".dom-p p.hidden").get(0); + const visibleP = Cypress.$(".dom-p p.visible").get(0); + + // our first paragraph has css class 'hidden' + expect(Cypress.dom.isHidden(hiddenP)).to.be.true; + expect(Cypress.dom.isHidden(visibleP)).to.be.false; + }); +}); + +context("Cypress.env()", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + // We can set environment variables for highly dynamic values + + // https://on.cypress.io/environment-variables + it("Get environment variables", () => { + // https://on.cypress.io/env + // set multiple environment variables + Cypress.env({ + host: "veronica.dev.local", + api_server: "http://localhost:8888/v1/", + }); + + // get environment variable + expect(Cypress.env("host")).to.eq("veronica.dev.local"); + + // set environment variable + Cypress.env("api_server", "http://localhost:8888/v2/"); + expect(Cypress.env("api_server")).to.eq("http://localhost:8888/v2/"); + + // get all environment variable + expect(Cypress.env()).to.have.property("host", "veronica.dev.local"); + expect(Cypress.env()).to.have.property( + "api_server", + "http://localhost:8888/v2/", + ); + }); +}); + +context("Cypress.log", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + it("Control what is printed to the Command Log", () => { + // https://on.cypress.io/cypress-log + }); +}); + +context("Cypress.platform", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + it("Get underlying OS name", () => { + // https://on.cypress.io/platform + expect(Cypress.platform).to.be.exist; + }); +}); + +context("Cypress.version", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + it("Get current version of Cypress being run", () => { + // https://on.cypress.io/version + expect(Cypress.version).to.be.exist; + }); +}); + +context("Cypress.spec", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); + + it("Get current spec information", () => { + // https://on.cypress.io/spec + // wrap the object so we can inspect it easily by clicking in the command log + cy.wrap(Cypress.spec).should("have.keys", ["name", "relative", "absolute"]); + }); +}); diff --git a/app/client/cypress/integration/examples/files.spec.js b/app/client/cypress/integration/examples/files.spec.js new file mode 100644 index 0000000000..7eaed162c7 --- /dev/null +++ b/app/client/cypress/integration/examples/files.spec.js @@ -0,0 +1,119 @@ +/// + +/// JSON fixture file can be loaded directly using +// the built-in JavaScript bundler +// @ts-ignore +const requiredExample = require("../../fixtures/example"); + +context("Files", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/files"); + }); + + beforeEach(() => { + // load example.json fixture file and store + // in the test context object + cy.fixture("example.json").as("example"); + }); + + it("cy.fixture() - load a fixture", () => { + // https://on.cypress.io/fixture + + // Instead of writing a response inline you can + // use a fixture file's content. + + cy.server(); + cy.fixture("example.json").as("comment"); + // when application makes an Ajax request matching "GET comments/*" + // Cypress will intercept it and reply with object + // from the "comment" alias + cy.route("GET", "comments/*", "@comment").as("getComment"); + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get(".fixture-btn").click(); + + cy.wait("@getComment") + .its("responseBody") + .should("have.property", "name") + .and("include", "Using fixtures to represent data"); + + // you can also just write the fixture in the route + cy.route("GET", "comments/*", "fixture:example.json").as("getComment"); + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get(".fixture-btn").click(); + + cy.wait("@getComment") + .its("responseBody") + .should("have.property", "name") + .and("include", "Using fixtures to represent data"); + + // or write fx to represent fixture + // by default it assumes it's .json + cy.route("GET", "comments/*", "fx:example").as("getComment"); + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get(".fixture-btn").click(); + + cy.wait("@getComment") + .its("responseBody") + .should("have.property", "name") + .and("include", "Using fixtures to represent data"); + }); + + it("cy.fixture() or require - load a fixture", function() { + // we are inside the "function () { ... }" + // callback and can use test context object "this" + // "this.example" was loaded in "beforeEach" function callback + expect(this.example, "fixture in the test context").to.deep.equal( + requiredExample, + ); + + // or use "cy.wrap" and "should('deep.equal', ...)" assertion + // @ts-ignore + cy.wrap(this.example, "fixture vs require").should( + "deep.equal", + requiredExample, + ); + }); + + it("cy.readFile() - read a files contents", () => { + // https://on.cypress.io/readfile + + // You can read a file and yield its contents + // The filePath is relative to your project's root. + cy.readFile("cypress.json").then(json => { + expect(json).to.be.an("object"); + }); + }); + + it("cy.writeFile() - write to a file", () => { + // https://on.cypress.io/writefile + + // You can write to a file + + // Use a response from a request to automatically + // generate a fixture file for use later + cy.request("https://jsonplaceholder.cypress.io/users").then(response => { + cy.writeFile("cypress/fixtures/users.json", response.body); + }); + cy.fixture("users").should(users => { + expect(users[0].name).to.exist; + }); + + // JavaScript arrays and objects are stringified + // and formatted into text. + cy.writeFile("cypress/fixtures/profile.json", { + id: 8739, + name: "Jane", + email: "jane@example.com", + }); + + cy.fixture("profile").should(profile => { + expect(profile.name).to.eq("Jane"); + }); + }); +}); diff --git a/app/client/cypress/integration/examples/local_storage.spec.js b/app/client/cypress/integration/examples/local_storage.spec.js new file mode 100644 index 0000000000..6826bbf506 --- /dev/null +++ b/app/client/cypress/integration/examples/local_storage.spec.js @@ -0,0 +1,58 @@ +/// + +context("Local Storage", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/local-storage"); + }); + // Although local storage is automatically cleared + // in between tests to maintain a clean state + // sometimes we need to clear the local storage manually + + it("cy.clearLocalStorage() - clear all data in local storage", () => { + // https://on.cypress.io/clearlocalstorage + cy.get(".ls-btn") + .click() + .should(() => { + expect(localStorage.getItem("prop1")).to.eq("red"); + expect(localStorage.getItem("prop2")).to.eq("blue"); + expect(localStorage.getItem("prop3")).to.eq("magenta"); + }); + + // clearLocalStorage() yields the localStorage object + cy.clearLocalStorage().should(ls => { + expect(ls.getItem("prop1")).to.be.null; + expect(ls.getItem("prop2")).to.be.null; + expect(ls.getItem("prop3")).to.be.null; + }); + + // Clear key matching string in Local Storage + cy.get(".ls-btn") + .click() + .should(() => { + expect(localStorage.getItem("prop1")).to.eq("red"); + expect(localStorage.getItem("prop2")).to.eq("blue"); + expect(localStorage.getItem("prop3")).to.eq("magenta"); + }); + + cy.clearLocalStorage("prop1").should(ls => { + expect(ls.getItem("prop1")).to.be.null; + expect(ls.getItem("prop2")).to.eq("blue"); + expect(ls.getItem("prop3")).to.eq("magenta"); + }); + + // Clear keys matching regex in Local Storage + cy.get(".ls-btn") + .click() + .should(() => { + expect(localStorage.getItem("prop1")).to.eq("red"); + expect(localStorage.getItem("prop2")).to.eq("blue"); + expect(localStorage.getItem("prop3")).to.eq("magenta"); + }); + + cy.clearLocalStorage(/prop1|2/).should(ls => { + expect(ls.getItem("prop1")).to.be.null; + expect(ls.getItem("prop2")).to.be.null; + expect(ls.getItem("prop3")).to.eq("magenta"); + }); + }); +}); diff --git a/app/client/cypress/integration/examples/location.spec.js b/app/client/cypress/integration/examples/location.spec.js new file mode 100644 index 0000000000..67e68e1ceb --- /dev/null +++ b/app/client/cypress/integration/examples/location.spec.js @@ -0,0 +1,34 @@ +/// + +context("Location", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/location"); + }); + + it("cy.hash() - get the current URL hash", () => { + // https://on.cypress.io/hash + cy.hash().should("be.empty"); + }); + + it("cy.location() - get window.location", () => { + // https://on.cypress.io/location + cy.location().should(location => { + expect(location.hash).to.be.empty; + expect(location.href).to.eq( + "https://example.cypress.io/commands/location", + ); + expect(location.host).to.eq("example.cypress.io"); + expect(location.hostname).to.eq("example.cypress.io"); + expect(location.origin).to.eq("https://example.cypress.io"); + expect(location.pathname).to.eq("/commands/location"); + expect(location.port).to.eq(""); + expect(location.protocol).to.eq("https:"); + expect(location.search).to.be.empty; + }); + }); + + it("cy.url() - get the current URL", () => { + // https://on.cypress.io/url + cy.url().should("eq", "https://example.cypress.io/commands/location"); + }); +}); diff --git a/app/client/cypress/integration/examples/misc.spec.js b/app/client/cypress/integration/examples/misc.spec.js new file mode 100644 index 0000000000..bc8af442f2 --- /dev/null +++ b/app/client/cypress/integration/examples/misc.spec.js @@ -0,0 +1,93 @@ +/// + +context("Misc", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/misc"); + }); + + it(".end() - end the command chain", () => { + // https://on.cypress.io/end + + // cy.end is useful when you want to end a chain of commands + // and force Cypress to re-query from the root element + cy.get(".misc-table").within(() => { + // ends the current chain and yields null + cy.contains("Cheryl") + .click() + .end(); + + // queries the entire table again + cy.contains("Charles").click(); + }); + }); + + it("cy.exec() - execute a system command", () => { + // https://on.cypress.io/exec + + // execute a system command. + // so you can take actions necessary for + // your test outside the scope of Cypress. + cy.exec("echo Jane Lane") + .its("stdout") + .should("contain", "Jane Lane"); + + // we can use Cypress.platform string to + // select appropriate command + // https://on.cypress/io/platform + cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`); + + if (Cypress.platform === "win32") { + cy.exec("print cypress.json") + .its("stderr") + .should("be.empty"); + } else { + cy.exec("cat cypress.json") + .its("stderr") + .should("be.empty"); + + cy.exec("pwd") + .its("code") + .should("eq", 0); + } + }); + + it("cy.focused() - get the DOM element that has focus", () => { + // https://on.cypress.io/focused + cy.get(".misc-form") + .find("#name") + .click(); + cy.focused().should("have.id", "name"); + + cy.get(".misc-form") + .find("#description") + .click(); + cy.focused().should("have.id", "description"); + }); + + context("Cypress.Screenshot", function() { + it("cy.screenshot() - take a screenshot", () => { + // https://on.cypress.io/screenshot + cy.screenshot("my-image"); + }); + + it("Cypress.Screenshot.defaults() - change default config of screenshots", function() { + Cypress.Screenshot.defaults({ + blackout: [".foo"], + capture: "viewport", + clip: { x: 0, y: 0, width: 200, height: 200 }, + scale: false, + disableTimersAndAnimations: true, + screenshotOnRunFailure: true, + beforeScreenshot() {}, + afterScreenshot() {}, + }); + }); + }); + + it("cy.wrap() - wrap an object", () => { + // https://on.cypress.io/wrap + cy.wrap({ foo: "bar" }) + .should("have.property", "foo") + .and("include", "bar"); + }); +}); diff --git a/app/client/cypress/integration/examples/navigation.spec.js b/app/client/cypress/integration/examples/navigation.spec.js new file mode 100644 index 0000000000..bfb05ef29e --- /dev/null +++ b/app/client/cypress/integration/examples/navigation.spec.js @@ -0,0 +1,60 @@ +/// + +context("Navigation", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io"); + cy.get(".navbar-nav") + .contains("Commands") + .click(); + cy.get(".dropdown-menu") + .contains("Navigation") + .click(); + }); + + it("cy.go() - go back or forward in the browser's history", () => { + // https://on.cypress.io/go + + cy.location("pathname").should("include", "navigation"); + + cy.go("back"); + cy.location("pathname").should("not.include", "navigation"); + + cy.go("forward"); + cy.location("pathname").should("include", "navigation"); + + // clicking back + cy.go(-1); + cy.location("pathname").should("not.include", "navigation"); + + // clicking forward + cy.go(1); + cy.location("pathname").should("include", "navigation"); + }); + + it("cy.reload() - reload the page", () => { + // https://on.cypress.io/reload + cy.reload(); + + // reload the page without using the cache + cy.reload(true); + }); + + it("cy.visit() - visit a remote url", () => { + // https://on.cypress.io/visit + + // Visit any sub-domain of your current domain + + // Pass options to the visit + cy.visit("https://example.cypress.io/commands/navigation", { + timeout: 50000, // increase total time for the visit to resolve + onBeforeLoad(contentWindow) { + // contentWindow is the remote page's window object + expect(typeof contentWindow === "object").to.be.true; + }, + onLoad(contentWindow) { + // contentWindow is the remote page's window object + expect(typeof contentWindow === "object").to.be.true; + }, + }); + }); +}); diff --git a/app/client/cypress/integration/examples/network_requests.spec.js b/app/client/cypress/integration/examples/network_requests.spec.js new file mode 100644 index 0000000000..c96cbb228f --- /dev/null +++ b/app/client/cypress/integration/examples/network_requests.spec.js @@ -0,0 +1,217 @@ +/// + +context("Network Requests", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/network-requests"); + }); + + // Manage AJAX / XHR requests in your app + + it("cy.server() - control behavior of network requests and responses", () => { + // https://on.cypress.io/server + + cy.server().should(server => { + // the default options on server + // you can override any of these options + expect(server.delay).to.eq(0); + expect(server.method).to.eq("GET"); + expect(server.status).to.eq(200); + expect(server.headers).to.be.null; + expect(server.response).to.be.null; + expect(server.onRequest).to.be.undefined; + expect(server.onResponse).to.be.undefined; + expect(server.onAbort).to.be.undefined; + + // These options control the server behavior + // affecting all requests + + // pass false to disable existing route stubs + expect(server.enable).to.be.true; + // forces requests that don't match your routes to 404 + expect(server.force404).to.be.false; + // whitelists requests from ever being logged or stubbed + expect(server.whitelist).to.be.a("function"); + }); + + cy.server({ + method: "POST", + delay: 1000, + status: 422, + response: {}, + }); + + // any route commands will now inherit the above options + // from the server. anything we pass specifically + // to route will override the defaults though. + }); + + it("cy.request() - make an XHR request", () => { + // https://on.cypress.io/request + cy.request("https://jsonplaceholder.cypress.io/comments").should( + response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.length(500); + expect(response).to.have.property("headers"); + expect(response).to.have.property("duration"); + }, + ); + }); + + it("cy.request() - verify response using BDD syntax", () => { + cy.request("https://jsonplaceholder.cypress.io/comments").then(response => { + // https://on.cypress.io/assertions + expect(response) + .property("status") + .to.equal(200); + expect(response) + .property("body") + .to.have.length(500); + expect(response).to.include.keys("headers", "duration"); + }); + }); + + it("cy.request() with query parameters", () => { + // will execute request + // https://jsonplaceholder.cypress.io/comments?postId=1&id=3 + cy.request({ + url: "https://jsonplaceholder.cypress.io/comments", + qs: { + postId: 1, + id: 3, + }, + }) + .its("body") + .should("be.an", "array") + .and("have.length", 1) + .its("0") // yields first element of the array + .should("contain", { + postId: 1, + id: 3, + }); + }); + + it("cy.request() - pass result to the second request", () => { + // first, let's find out the userId of the first user we have + cy.request("https://jsonplaceholder.cypress.io/users?_limit=1") + .its("body") // yields the response object + .its("0") // yields the first element of the returned list + // the above two commands its('body').its('0') + // can be written as its('body.0') + // if you do not care about TypeScript checks + .then(user => { + expect(user) + .property("id") + .to.be.a("number"); + // make a new post on behalf of the user + cy.request("POST", "https://jsonplaceholder.cypress.io/posts", { + userId: user.id, + title: "Cypress Test Runner", + body: + "Fast, easy and reliable testing for anything that runs in a browser.", + }); + }) + // note that the value here is the returned value of the 2nd request + // which is the new post object + .then(response => { + expect(response) + .property("status") + .to.equal(201); // new entity created + expect(response) + .property("body") + .to.contain({ + id: 101, // there are already 100 posts, so new entity gets id 101 + title: "Cypress Test Runner", + }); + // we don't know the user id here - since it was in above closure + // so in this test just confirm that the property is there + expect(response.body) + .property("userId") + .to.be.a("number"); + }); + }); + + it("cy.request() - save response in the shared test context", () => { + // https://on.cypress.io/variables-and-aliases + cy.request("https://jsonplaceholder.cypress.io/users?_limit=1") + .its("body") + .its("0") // yields the first element of the returned list + .as("user") // saves the object in the test context + .then(function() { + // NOTE đŸ‘€ + // By the time this callback runs the "as('user')" command + // has saved the user object in the test context. + // To access the test context we need to use + // the "function () { ... }" callback form, + // otherwise "this" points at a wrong or undefined object! + cy.request("POST", "https://jsonplaceholder.cypress.io/posts", { + userId: this.user.id, + title: "Cypress Test Runner", + body: + "Fast, easy and reliable testing for anything that runs in a browser.", + }) + .its("body") + .as("post"); // save the new post from the response + }) + .then(function() { + // When this callback runs, both "cy.request" API commands have finished + // and the test context has "user" and "post" objects set. + // Let's verify them. + expect(this.post, "post has the right user id") + .property("userId") + .to.equal(this.user.id); + }); + }); + + it("cy.route() - route responses to matching requests", () => { + // https://on.cypress.io/route + + const message = "whoa, this comment does not exist"; + + cy.server(); + + // Listen to GET to comments/1 + cy.route("GET", "comments/*").as("getComment"); + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get(".network-btn").click(); + + // https://on.cypress.io/wait + cy.wait("@getComment") + .its("status") + .should("eq", 200); + + // Listen to POST to comments + cy.route("POST", "/comments").as("postComment"); + + // we have code that posts a comment when + // the button is clicked in scripts.js + cy.get(".network-post").click(); + cy.wait("@postComment").should(xhr => { + expect(xhr.requestBody).to.include("email"); + expect(xhr.requestHeaders).to.have.property("Content-Type"); + expect(xhr.responseBody).to.have.property( + "name", + "Using POST in cy.route()", + ); + }); + + // Stub a response to PUT comments/ **** + cy.route({ + method: "PUT", + url: "comments/*", + status: 404, + response: { error: message }, + delay: 500, + }).as("putComment"); + + // we have code that puts a comment when + // the button is clicked in scripts.js + cy.get(".network-put").click(); + + cy.wait("@putComment"); + + // our 404 statusCode logic in scripts.js executed + cy.get(".network-put-comment").should("contain", message); + }); +}); diff --git a/app/client/cypress/integration/examples/querying.spec.js b/app/client/cypress/integration/examples/querying.spec.js new file mode 100644 index 0000000000..897298a443 --- /dev/null +++ b/app/client/cypress/integration/examples/querying.spec.js @@ -0,0 +1,114 @@ +/// + +context("Querying", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/querying"); + }); + + // The most commonly used query is 'cy.get()', you can + // think of this like the '$' in jQuery + + it("cy.get() - query DOM elements", () => { + // https://on.cypress.io/get + + cy.get("#query-btn").should("contain", "Button"); + + cy.get(".query-btn").should("contain", "Button"); + + cy.get("#querying .well>button:first").should("contain", "Button"); + // ↲ + // Use CSS selectors just like jQuery + + cy.get('[data-test-id="test-example"]').should("have.class", "example"); + + // 'cy.get()' yields jQuery object, you can get its attribute + // by invoking `.attr()` method + cy.get('[data-test-id="test-example"]') + .invoke("attr", "data-test-id") + .should("equal", "test-example"); + + // or you can get element's CSS property + cy.get('[data-test-id="test-example"]') + .invoke("css", "position") + .should("equal", "static"); + + // or use assertions directly during 'cy.get()' + // https://on.cypress.io/assertions + cy.get('[data-test-id="test-example"]') + .should("have.attr", "data-test-id", "test-example") + .and("have.css", "position", "static"); + }); + + it("cy.contains() - query DOM elements with matching content", () => { + // https://on.cypress.io/contains + cy.get(".query-list") + .contains("bananas") + .should("have.class", "third"); + + // we can pass a regexp to `.contains()` + cy.get(".query-list") + .contains(/^b\w+/) + .should("have.class", "third"); + + cy.get(".query-list") + .contains("apples") + .should("have.class", "first"); + + // passing a selector to contains will + // yield the selector containing the text + cy.get("#querying") + .contains("ul", "oranges") + .should("have.class", "query-list"); + + cy.get(".query-button") + .contains("Save Form") + .should("have.class", "btn"); + }); + + it(".within() - query DOM elements within a specific element", () => { + // https://on.cypress.io/within + cy.get(".query-form").within(() => { + cy.get("input:first").should("have.attr", "placeholder", "Email"); + cy.get("input:last").should("have.attr", "placeholder", "Password"); + }); + }); + + it("cy.root() - query the root DOM element", () => { + // https://on.cypress.io/root + + // By default, root is the document + cy.root().should("match", "html"); + + cy.get(".query-ul").within(() => { + // In this within, the root is now the ul DOM element + cy.root().should("have.class", "query-ul"); + }); + }); + + it("best practices - selecting elements", () => { + // https://on.cypress.io/best-practices#Selecting-Elements + cy.get("[data-cy=best-practices-selecting-elements]").within(() => { + // Worst - too generic, no context + cy.get("button").click(); + + // Bad. Coupled to styling. Highly subject to change. + cy.get(".btn.btn-large").click(); + + // Average. Coupled to the `name` attribute which has HTML semantics. + cy.get("[name=submission]").click(); + + // Better. But still coupled to styling or JS event listeners. + cy.get("#main").click(); + + // Slightly better. Uses an ID but also ensures the element + // has an ARIA role attribute + cy.get("#main[role=button]").click(); + + // Much better. But still coupled to text content that may change. + cy.contains("Submit").click(); + + // Best. Insulated from all changes. + cy.get("[data-cy=submit]").click(); + }); + }); +}); diff --git a/app/client/cypress/integration/examples/spies_stubs_clocks.spec.js b/app/client/cypress/integration/examples/spies_stubs_clocks.spec.js new file mode 100644 index 0000000000..53e84a08d5 --- /dev/null +++ b/app/client/cypress/integration/examples/spies_stubs_clocks.spec.js @@ -0,0 +1,98 @@ +/// + +context("Spies, Stubs, and Clock", () => { + it("cy.spy() - wrap a method in a spy", () => { + // https://on.cypress.io/spy + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); + + const obj = { + foo() {}, + }; + + const spy = cy.spy(obj, "foo").as("anyArgs"); + + obj.foo(); + + expect(spy).to.be.called; + }); + + it("cy.spy() retries until assertions pass", () => { + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); + + const obj = { + /** + * Prints the argument passed + * @param x {any} + */ + foo(x) { + console.log("obj.foo called with", x); + }, + }; + + cy.spy(obj, "foo").as("foo"); + + setTimeout(() => { + obj.foo("first"); + }, 500); + + setTimeout(() => { + obj.foo("second"); + }, 2500); + + cy.get("@foo").should("have.been.calledTwice"); + }); + + it("cy.stub() - create a stub and/or replace a function with stub", () => { + // https://on.cypress.io/stub + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); + + const obj = { + /** + * prints both arguments to the console + * @param a {string} + * @param b {string} + */ + foo(a, b) { + console.log("a", a, "b", b); + }, + }; + + const stub = cy.stub(obj, "foo").as("foo"); + + obj.foo("foo", "bar"); + + expect(stub).to.be.called; + }); + + it("cy.clock() - control time in the browser", () => { + // https://on.cypress.io/clock + + // create the date in UTC so its always the same + // no matter what local timezone the browser is running in + const now = new Date(Date.UTC(2017, 2, 14)).getTime(); + + cy.clock(now); + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); + cy.get("#clock-div") + .click() + .should("have.text", "1489449600"); + }); + + it("cy.tick() - move time in the browser", () => { + // https://on.cypress.io/tick + + // create the date in UTC so its always the same + // no matter what local timezone the browser is running in + const now = new Date(Date.UTC(2017, 2, 14)).getTime(); + + cy.clock(now); + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); + cy.get("#tick-div") + .click() + .should("have.text", "1489449600"); + cy.tick(10000); // 10 seconds passed + cy.get("#tick-div") + .click() + .should("have.text", "1489449610"); + }); +}); diff --git a/app/client/cypress/integration/examples/traversal.spec.js b/app/client/cypress/integration/examples/traversal.spec.js new file mode 100644 index 0000000000..9108b0efbb --- /dev/null +++ b/app/client/cypress/integration/examples/traversal.spec.js @@ -0,0 +1,140 @@ +/// + +context("Traversal", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/traversal"); + }); + + it(".children() - get child DOM elements", () => { + // https://on.cypress.io/children + cy.get(".traversal-breadcrumb") + .children(".active") + .should("contain", "Data"); + }); + + it(".closest() - get closest ancestor DOM element", () => { + // https://on.cypress.io/closest + cy.get(".traversal-badge") + .closest("ul") + .should("have.class", "list-group"); + }); + + it(".eq() - get a DOM element at a specific index", () => { + // https://on.cypress.io/eq + cy.get(".traversal-list>li") + .eq(1) + .should("contain", "siamese"); + }); + + it(".filter() - get DOM elements that match the selector", () => { + // https://on.cypress.io/filter + cy.get(".traversal-nav>li") + .filter(".active") + .should("contain", "About"); + }); + + it(".find() - get descendant DOM elements of the selector", () => { + // https://on.cypress.io/find + cy.get(".traversal-pagination") + .find("li") + .find("a") + .should("have.length", 7); + }); + + it(".first() - get first DOM element", () => { + // https://on.cypress.io/first + cy.get(".traversal-table td") + .first() + .should("contain", "1"); + }); + + it(".last() - get last DOM element", () => { + // https://on.cypress.io/last + cy.get(".traversal-buttons .btn") + .last() + .should("contain", "Submit"); + }); + + it(".next() - get next sibling DOM element", () => { + // https://on.cypress.io/next + cy.get(".traversal-ul") + .contains("apples") + .next() + .should("contain", "oranges"); + }); + + it(".nextAll() - get all next sibling DOM elements", () => { + // https://on.cypress.io/nextall + cy.get(".traversal-next-all") + .contains("oranges") + .nextAll() + .should("have.length", 3); + }); + + it(".nextUntil() - get next sibling DOM elements until next el", () => { + // https://on.cypress.io/nextuntil + cy.get("#veggies") + .nextUntil("#nuts") + .should("have.length", 3); + }); + + it(".not() - remove DOM elements from set of DOM elements", () => { + // https://on.cypress.io/not + cy.get(".traversal-disabled .btn") + .not("[disabled]") + .should("not.contain", "Disabled"); + }); + + it(".parent() - get parent DOM element from DOM elements", () => { + // https://on.cypress.io/parent + cy.get(".traversal-mark") + .parent() + .should("contain", "Morbi leo risus"); + }); + + it(".parents() - get parent DOM elements from DOM elements", () => { + // https://on.cypress.io/parents + cy.get(".traversal-cite") + .parents() + .should("match", "blockquote"); + }); + + it(".parentsUntil() - get parent DOM elements from DOM elements until el", () => { + // https://on.cypress.io/parentsuntil + cy.get(".clothes-nav") + .find(".active") + .parentsUntil(".clothes-nav") + .should("have.length", 2); + }); + + it(".prev() - get previous sibling DOM element", () => { + // https://on.cypress.io/prev + cy.get(".birds") + .find(".active") + .prev() + .should("contain", "Lorikeets"); + }); + + it(".prevAll() - get all previous sibling DOM elements", () => { + // https://on.cypress.io/prevAll + cy.get(".fruits-list") + .find(".third") + .prevAll() + .should("have.length", 2); + }); + + it(".prevUntil() - get all previous sibling DOM elements until el", () => { + // https://on.cypress.io/prevUntil + cy.get(".foods-list") + .find("#nuts") + .prevUntil("#veggies") + .should("have.length", 3); + }); + + it(".siblings() - get all sibling DOM elements", () => { + // https://on.cypress.io/siblings + cy.get(".traversal-pills .active") + .siblings() + .should("have.length", 2); + }); +}); diff --git a/app/client/cypress/integration/examples/utilities.spec.js b/app/client/cypress/integration/examples/utilities.spec.js new file mode 100644 index 0000000000..e69a96406e --- /dev/null +++ b/app/client/cypress/integration/examples/utilities.spec.js @@ -0,0 +1,145 @@ +/// + +context("Utilities", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/utilities"); + }); + + it("Cypress._ - call a lodash method", () => { + // https://on.cypress.io/_ + cy.request("https://jsonplaceholder.cypress.io/users").then(response => { + const ids = Cypress._.chain(response.body) + .map("id") + .take(3) + .value(); + + expect(ids).to.deep.eq([1, 2, 3]); + }); + }); + + it("Cypress.$ - call a jQuery method", () => { + // https://on.cypress.io/$ + const $li = Cypress.$(".utility-jquery li:first"); + + cy.wrap($li) + .should("not.have.class", "active") + .click() + .should("have.class", "active"); + }); + + it("Cypress.Blob - blob utilities and base64 string conversion", () => { + // https://on.cypress.io/blob + cy.get(".utility-blob").then($div => + // https://github.com/nolanlawson/blob-util#imgSrcToDataURL + // get the dataUrl string for the javascript-logo + Cypress.Blob.imgSrcToDataURL( + "https://example.cypress.io/assets/img/javascript-logo.png", + undefined, + "anonymous", + ).then(dataUrl => { + // create an element and set its src to the dataUrl + const img = Cypress.$("", { src: dataUrl }); + + // need to explicitly return cy here since we are initially returning + // the Cypress.Blob.imgSrcToDataURL promise to our test + // append the image + $div.append(img); + + cy.get(".utility-blob img") + .click() + .should("have.attr", "src", dataUrl); + }), + ); + }); + + it("Cypress.minimatch - test out glob patterns against strings", () => { + // https://on.cypress.io/minimatch + let matching = Cypress.minimatch("/users/1/comments", "/users/*/comments", { + matchBase: true, + }); + + expect(matching, "matching wildcard").to.be.true; + + matching = Cypress.minimatch("/users/1/comments/2", "/users/*/comments", { + matchBase: true, + }); + expect(matching, "comments").to.be.false; + + // ** matches against all downstream path segments + matching = Cypress.minimatch("/foo/bar/baz/123/quux?a=b&c=2", "/foo/**", { + matchBase: true, + }); + expect(matching, "comments").to.be.true; + + // whereas * matches only the next path segment + + matching = Cypress.minimatch("/foo/bar/baz/123/quux?a=b&c=2", "/foo/*", { + matchBase: false, + }); + expect(matching, "comments").to.be.false; + }); + + it("Cypress.moment() - format or parse dates using a moment method", () => { + // https://on.cypress.io/moment + const time = Cypress.moment("2014-04-25T19:38:53.196Z") + .utc() + .format("h:mm A"); + + expect(time).to.be.a("string"); + + cy.get(".utility-moment") + .contains("3:38 PM") + .should("have.class", "badge"); + + // the time in the element should be between 3pm and 5pm + const start = Cypress.moment("3:00 PM", "LT"); + const end = Cypress.moment("5:00 PM", "LT"); + + cy.get(".utility-moment .badge").should($el => { + // parse American time like "3:38 PM" + const m = Cypress.moment($el.text().trim(), "LT"); + + // display hours + minutes + AM|PM + const f = "h:mm A"; + + expect( + m.isBetween(start, end), + `${m.format(f)} should be between ${start.format(f)} and ${end.format( + f, + )}`, + ).to.be.true; + }); + }); + + it("Cypress.Promise - instantiate a bluebird promise", () => { + // https://on.cypress.io/promise + let waited = false; + + /** + * @return Bluebird + */ + function waitOneSecond() { + // return a promise that resolves after 1 second + // @ts-ignore TS2351 (new Cypress.Promise) + return new Cypress.Promise((resolve, reject) => { + setTimeout(() => { + // set waited to true + waited = true; + + // resolve with 'foo' string + resolve("foo"); + }, 1000); + }); + } + + cy.then(() => + // return a promise to cy.then() that + // is awaited until it resolves + // @ts-ignore TS7006 + waitOneSecond().then(str => { + expect(str).to.eq("foo"); + expect(waited).to.be.true; + }), + ); + }); +}); diff --git a/app/client/cypress/integration/examples/viewport.spec.js b/app/client/cypress/integration/examples/viewport.spec.js new file mode 100644 index 0000000000..1df56b0af5 --- /dev/null +++ b/app/client/cypress/integration/examples/viewport.spec.js @@ -0,0 +1,63 @@ +/// + +context("Viewport", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/viewport"); + }); + + it("cy.viewport() - set the viewport size and dimension", () => { + // https://on.cypress.io/viewport + + cy.get("#navbar").should("be.visible"); + cy.viewport(320, 480); + + // the navbar should have collapse since our screen is smaller + cy.get("#navbar").should("not.be.visible"); + cy.get(".navbar-toggle") + .should("be.visible") + .click(); + cy.get(".nav") + .find("a") + .should("be.visible"); + + // lets see what our app looks like on a super large screen + cy.viewport(2999, 2999); + + // cy.viewport() accepts a set of preset sizes + // to easily set the screen to a device's width and height + + // We added a cy.wait() between each viewport change so you can see + // the change otherwise it is a little too fast to see :) + + cy.viewport("macbook-15"); + cy.wait(200); + cy.viewport("macbook-13"); + cy.wait(200); + cy.viewport("macbook-11"); + cy.wait(200); + cy.viewport("ipad-2"); + cy.wait(200); + cy.viewport("ipad-mini"); + cy.wait(200); + cy.viewport("iphone-6+"); + cy.wait(200); + cy.viewport("iphone-6"); + cy.wait(200); + cy.viewport("iphone-5"); + cy.wait(200); + cy.viewport("iphone-4"); + cy.wait(200); + cy.viewport("iphone-3"); + cy.wait(200); + + // cy.viewport() accepts an orientation for all presets + // the default orientation is 'portrait' + cy.viewport("ipad-2", "portrait"); + cy.wait(200); + cy.viewport("iphone-4", "landscape"); + cy.wait(200); + + // The viewport will be reset back to the default dimensions + // in between tests (the default can be set in cypress.json) + }); +}); diff --git a/app/client/cypress/integration/examples/waiting.spec.js b/app/client/cypress/integration/examples/waiting.spec.js new file mode 100644 index 0000000000..108db7cbb0 --- /dev/null +++ b/app/client/cypress/integration/examples/waiting.spec.js @@ -0,0 +1,35 @@ +/// + +context("Waiting", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/waiting"); + }); + // BE CAREFUL of adding unnecessary wait times. + // https://on.cypress.io/best-practices#Unnecessary-Waiting + + // https://on.cypress.io/wait + it("cy.wait() - wait for a specific amount of time", () => { + cy.get(".wait-input1").type("Wait 1000ms after typing"); + cy.wait(1000); + cy.get(".wait-input2").type("Wait 1000ms after typing"); + cy.wait(1000); + cy.get(".wait-input3").type("Wait 1000ms after typing"); + cy.wait(1000); + }); + + it("cy.wait() - wait for a specific route", () => { + cy.server(); + + // Listen to GET to comments/1 + cy.route("GET", "comments/*").as("getComment"); + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get(".network-btn").click(); + + // wait for GET comments/1 + cy.wait("@getComment") + .its("status") + .should("eq", 200); + }); +}); diff --git a/app/client/cypress/integration/examples/window.spec.js b/app/client/cypress/integration/examples/window.spec.js new file mode 100644 index 0000000000..2d6d7fc268 --- /dev/null +++ b/app/client/cypress/integration/examples/window.spec.js @@ -0,0 +1,24 @@ +/// + +context("Window", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/window"); + }); + + it("cy.window() - get the global window object", () => { + // https://on.cypress.io/window + cy.window().should("have.property", "top"); + }); + + it("cy.document() - get the document object", () => { + // https://on.cypress.io/document + cy.document() + .should("have.property", "charset") + .and("eq", "UTF-8"); + }); + + it("cy.title() - get the title", () => { + // https://on.cypress.io/title + cy.title().should("include", "Kitchen Sink"); + }); +}); diff --git a/app/client/cypress/integration/test.js b/app/client/cypress/integration/test.js new file mode 100644 index 0000000000..7ef2207c9e --- /dev/null +++ b/app/client/cypress/integration/test.js @@ -0,0 +1,43 @@ +describe("My First Test", function() { + it("Visits the Kitchen Sink", function() { + cy.visit( + "http://localhost:3000/applications/5d807e45795dc6000482bc74/pages/5df08f966ccd2400049dde1a/edit", + ); + // cy.contains("Enter your Netlify Site URL"); + // cy.get(); + // Main button pressed (usually the left button) + // cy.get(".TABLE_WIDGET") + // .first() + // .trigger("mousedown") + // .trigger("mousemove", { which: 1, clientX: 400, clientY: 100 }) + // .trigger("mouseup"); + + // cy.contains("Submit").click(); + // cy.contains("Barbra", { timeout: 1000000 }).click(); + // // cy.contains("{{fetchUsers2}}"); + // cy.get('input[value="{{fetchUsers2}}"]') + // // .first() + // .clear() + // .type( + // "{{}{{}fetchUsers2.map(user => {{}return {{}'name': user.name}})}}", + // { + // force: true, + // }, + // ); + + // .then(function($input) { + // $input[0].setAttribute( + // "value", + // "{{fetchUsers2.map(user => {return {'name': user.name}})}}", + // ); + // }); + + // .invoke( + // "attr", + // "value", + // "{{fetchUsers2.map(user => {return {'name': user.name}})}}", + // ); + + expect(true).to.eq(true); + }); +}); diff --git a/app/client/cypress/plugins/index.js b/app/client/cypress/plugins/index.js new file mode 100644 index 0000000000..dffed2532f --- /dev/null +++ b/app/client/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/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js new file mode 100644 index 0000000000..ca4d256f3e --- /dev/null +++ b/app/client/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 will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/app/client/cypress/support/index.js b/app/client/cypress/support/index.js new file mode 100644 index 0000000000..d68db96df2 --- /dev/null +++ b/app/client/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// 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') diff --git a/app/client/package.json b/app/client/package.json index 3fdcb70cda..3df7d547cb 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -91,6 +91,7 @@ "eject": "react-scripts eject", "start-prod": "REACT_APP_ENVIRONMENT=PRODUCTION craco start", "storybook": "start-storybook", + "cytest": "REACT_APP_TESTING=TESTING REACT_APP_ENVIRONMENT=DEVELOPMENT craco start & ./node_modules/.bin/cypress open", "build-storybook": "build-storybook -c .storybook -o .storybook-out" }, "resolution": { @@ -121,6 +122,7 @@ "@typescript-eslint/eslint-plugin": "^2.0.0", "@typescript-eslint/parser": "^2.0.0", "babel-loader": "^8.0.6", + "cypress": "^3.7.0", "dotenv": "^8.1.0", "eslint": "^6.4.0", "eslint-config-prettier": "^6.1.0", diff --git a/app/client/src/pages/common/ProtectedRoute.tsx b/app/client/src/pages/common/ProtectedRoute.tsx index 8c07524ddf..dc521a34cb 100644 --- a/app/client/src/pages/common/ProtectedRoute.tsx +++ b/app/client/src/pages/common/ProtectedRoute.tsx @@ -12,15 +12,14 @@ const ProtectedRoute = ({ component: React.ReactType; exact?: boolean; }) => { + const shouldShowLogin = + !_.isNil(netlifyIdentity.currentUser()) || + process.env.REACT_APP_TESTING === "TESTING"; return ( - !_.isNil(netlifyIdentity.currentUser()) ? ( - - ) : ( - - ) + shouldShowLogin ? : } /> ); diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 89e0074b16..2ef7ca7607 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -1096,6 +1096,24 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-9.0.1.tgz#c27b391d8457d1e893f1eddeaf5e5412d12ffbb5" integrity sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA== +"@cypress/listr-verbose-renderer@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" + integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo= + dependencies: + chalk "^1.1.3" + cli-cursor "^1.0.2" + date-fns "^1.27.2" + figures "^1.7.0" + +"@cypress/xvfb@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" + integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== + dependencies: + debug "^3.1.0" + lodash.once "^4.1.1" + "@emotion/cache@^10.0.17", "@emotion/cache@^10.0.9": version "10.0.19" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.19.tgz#d258d94d9c707dcadaf1558def968b86bb87ad71" @@ -2426,6 +2444,11 @@ resolved "https://registry.yarnpkg.com/@types/shallowequal/-/shallowequal-1.1.1.tgz#aad262bb3f2b1257d94c71d545268d592575c9b1" integrity sha512-Lhni3aX80zbpdxRuWhnuYPm8j8UQaa571lHP/xI4W+7BAFhSIhRReXnqjEgT/XzPoXZTJkCqstFMJ8CZTK6IlQ== +"@types/sizzle@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" + integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -3011,6 +3034,11 @@ ansi-colors@^3.0.0, ansi-colors@^3.2.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== +ansi-escapes@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= + ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -3098,6 +3126,11 @@ aproba@^1.0.3, aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +arch@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" + integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -3295,6 +3328,13 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" + integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== + dependencies: + lodash "^4.17.10" + async@^2.1.4, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -3856,6 +3896,11 @@ block-stream@*: dependencies: inherits "~2.0.0" +bluebird@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" + integrity sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw= + bluebird@^3.3.5, bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -4057,6 +4102,11 @@ btoa@^1.2.1: resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g== +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -4137,6 +4187,13 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cachedir@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-1.3.0.tgz#5e01928bf2d95b5edd94b0942188246740e0dbc4" + integrity sha512-O1ji32oyON9laVPJL1IZ5bmwd2cB46VfpxkDequezH+15FDzzVddEyrGEeX4WusDSqKxdyFdDQDEG1yo1GoWkg== + dependencies: + os-homedir "^1.0.1" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -4312,6 +4369,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +check-more-types@2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" + integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= + chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.4: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -4343,6 +4405,11 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" +ci-info@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -4388,6 +4455,13 @@ cli-boxes@^2.2.0: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== +cli-cursor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= + dependencies: + restore-cursor "^1.0.1" + cli-cursor@^2.0.0, cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -4402,6 +4476,11 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-spinners@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" + integrity sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw= + cli-spinners@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" @@ -4594,6 +4673,11 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz#419cd7fb3258b1ed838dc0953167a25e152f5b59" integrity sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ== +commander@2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== + commander@2.17.x: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -4614,7 +4698,7 @@ commander@~2.19.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== -common-tags@^1.8.0: +common-tags@1.8.0, common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== @@ -4661,7 +4745,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0: +concat-stream@1.6.2, concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -5208,6 +5292,44 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= +cypress@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.7.0.tgz#e2cd71b87b6ce0d4c72c6ea25da1005d75c1f231" + integrity sha512-o+vfRxqAba8TduelzfZQ4WHmj2yNEjaoO2EuZ8dZ9pJpuW+WGtBGheKIp6zkoQsp8ZgFe8OoHh1i2mY8BDnMAw== + dependencies: + "@cypress/listr-verbose-renderer" "0.4.1" + "@cypress/xvfb" "1.2.4" + "@types/sizzle" "2.3.2" + arch "2.1.1" + bluebird "3.5.0" + cachedir "1.3.0" + chalk "2.4.2" + check-more-types "2.24.0" + commander "2.15.1" + common-tags "1.8.0" + debug "3.2.6" + execa "0.10.0" + executable "4.1.1" + extract-zip "1.6.7" + fs-extra "5.0.0" + getos "3.1.1" + is-ci "1.2.1" + is-installed-globally "0.1.0" + lazy-ass "1.6.0" + listr "0.12.0" + lodash "4.17.15" + log-symbols "2.2.0" + minimist "1.2.0" + moment "2.24.0" + ramda "0.24.1" + request "2.88.0" + request-progress "3.0.0" + supports-color "5.5.0" + tmp "0.1.0" + untildify "3.0.3" + url "0.11.0" + yauzl "2.10.0" + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -5249,6 +5371,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6. dependencies: ms "2.0.0" +debug@3.2.6, debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -5256,13 +5385,6 @@ debug@=3.1.0: dependencies: ms "2.0.0" -debug@^3.0.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -6186,6 +6308,19 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== +execa@0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" + integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== + dependencies: + cross-spawn "^6.0.0" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" @@ -6227,11 +6362,23 @@ execa@^2.0.3: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +executable@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" + integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== + dependencies: + pify "^2.2.0" + exif-js@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/exif-js/-/exif-js-2.3.0.tgz#9d10819bf571f873813e7640241255ab9ce1a814" integrity sha1-nRCBm/Vx+HOBPnZAJBJVq5zhqBQ= +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -6348,6 +6495,16 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-zip@1.6.7: + version "1.6.7" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" + integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= + dependencies: + concat-stream "1.6.2" + debug "2.6.9" + mkdirp "0.5.1" + yauzl "2.4.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -6454,6 +6611,20 @@ fbjs@^0.8.0: setimmediate "^1.0.5" ua-parser-js "^0.7.18" +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= + dependencies: + pend "~1.2.0" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -6733,6 +6904,15 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" +fs-extra@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" + integrity sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@7.0.1, fs-extra@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -6921,6 +7101,13 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +getos@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.1.tgz#967a813cceafee0156b0483f7cffa5b3eff029c5" + integrity sha512-oUP1rnEhAr97rkitiszGP9EgDVYnmchgFzfqRzSkgtfv7ai6tEi7Ko8GgjNXts7VLWEqrTWyhsOKLe5C5b/Zkg== + dependencies: + async "2.6.1" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -6960,6 +7147,13 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl once "^1.3.0" path-is-absolute "^1.0.0" +global-dirs@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= + dependencies: + ini "^1.3.4" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -7692,7 +7886,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.5, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -7878,6 +8072,13 @@ is-callable@^1.1.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-ci@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" + integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== + dependencies: + ci-info "^1.5.0" + is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -8019,6 +8220,14 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz#e8a426a69b6d31470d3a33a47bb825cda02506ee" integrity sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA== +is-installed-globally@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= + dependencies: + global-dirs "^0.1.0" + is-path-inside "^1.0.0" + is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" @@ -8962,6 +9171,11 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" +lazy-ass@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" + integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= + lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -9045,6 +9259,20 @@ listr-silent-renderer@^1.1.1: resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= +listr-update-renderer@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9" + integrity sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk= + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + elegant-spinner "^1.0.1" + figures "^1.7.0" + indent-string "^3.0.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + strip-ansi "^3.0.1" + listr-update-renderer@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2" @@ -9059,6 +9287,16 @@ listr-update-renderer@^0.5.0: log-update "^2.3.0" strip-ansi "^3.0.1" +listr-verbose-renderer@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35" + integrity sha1-ggb0z21S3cWCfl/RSYng6WWTOjU= + dependencies: + chalk "^1.1.3" + cli-cursor "^1.0.2" + date-fns "^1.27.2" + figures "^1.7.0" + listr-verbose-renderer@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db" @@ -9069,6 +9307,28 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" +listr@0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a" + integrity sha1-a84sD1YD+klYDqF81qAMwOX6RRo= + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + figures "^1.7.0" + indent-string "^2.1.0" + is-promise "^2.1.0" + is-stream "^1.1.0" + listr-silent-renderer "^1.1.1" + listr-update-renderer "^0.2.0" + listr-verbose-renderer "^0.4.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + ora "^0.2.3" + p-map "^1.1.1" + rxjs "^5.0.0-beta.11" + stream-to-observable "^0.1.0" + strip-ansi "^3.0.1" + listr@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" @@ -9225,11 +9485,18 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.0.1, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5, lodash@^4.2.0, lodash@~4.17.10: +lodash@4.17.15, "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.0.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5, lodash@^4.2.0, lodash@~4.17.10: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +log-symbols@2.2.0, log-symbols@^2.1.0, log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -9237,13 +9504,6 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" -log-symbols@^2.1.0, log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - log-symbols@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" @@ -9251,6 +9511,14 @@ log-symbols@^3.0.0: dependencies: chalk "^2.4.2" +log-update@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1" + integrity sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE= + dependencies: + ansi-escapes "^1.0.0" + cli-cursor "^1.0.2" + log-update@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" @@ -9698,7 +9966,7 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= @@ -9769,7 +10037,7 @@ moment-timezone@^0.5.26, moment-timezone@^0.5.27: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@>=2.14.0, moment@^2.24.0: +moment@2.24.0, "moment@>= 2.9.0", moment@>=2.14.0, moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -10319,6 +10587,11 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= + onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -10387,6 +10660,16 @@ optionator@^0.8.1, optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" +ora@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" + integrity sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q= + dependencies: + chalk "^1.1.1" + cli-cursor "^1.0.2" + cli-spinners "^0.1.2" + object-assign "^4.0.1" + ora@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" @@ -10411,7 +10694,7 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0: +os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= @@ -10752,6 +11035,11 @@ pdfjs-dist@2.1.266: node-ensure "^0.0.0" worker-loader "^2.0.0" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -10762,7 +11050,7 @@ picomatch@^2.0.5: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.1.tgz#ecdfbea7704adb5fe6fb47f9866c4c0e15e905c5" integrity sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA== -pify@^2.0.0: +pify@^2.0.0, pify@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= @@ -11852,6 +12140,11 @@ raf@3.4.1: dependencies: performance-now "^2.1.0" +ramda@0.24.1: + version "0.24.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857" + integrity sha1-w7d1UZfzW43DUCIoJixMkd22uFc= + ramda@^0.21.0: version "0.21.0" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35" @@ -12797,6 +13090,13 @@ replace-ext@1.0.0: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= +request-progress@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" + integrity sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4= + dependencies: + throttleit "^1.0.0" + request-promise-core@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" @@ -12813,7 +13113,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.87.0, request@^2.88.0: +request@2.88.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -12943,6 +13243,14 @@ resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, dependencies: path-parse "^1.0.6" +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -13162,6 +13470,13 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" +rxjs@^5.0.0-beta.11: + version "5.5.12" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" + integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== + dependencies: + symbol-observable "1.0.1" + rxjs@^6.3.3, rxjs@^6.4.0: version "6.5.3" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" @@ -13875,6 +14190,11 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= +stream-to-observable@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" + integrity sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4= + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -14140,18 +14460,18 @@ stylis@^3.5.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^5.3.0, supports-color@^5.5.0: +supports-color@5.5.0, supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" @@ -14190,6 +14510,11 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" +symbol-observable@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" + integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= + symbol-observable@^1.0.2, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -14331,6 +14656,11 @@ throttle-debounce@^2.1.0: resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5" integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg== +throttleit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" + integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw= + through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -14394,6 +14724,13 @@ tinycolor2@^1.4.1: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g= +tmp@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" + integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== + dependencies: + rimraf "^2.6.3" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -14905,6 +15242,11 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +untildify@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" + integrity sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA== + upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" @@ -14953,7 +15295,7 @@ url-parse@^1.4.3: querystringify "^2.1.1" requires-port "^1.0.0" -url@^0.11.0: +url@0.11.0, url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= @@ -15777,6 +16119,21 @@ yargs@^7.0.0: y18n "^3.2.1" yargs-parser "^5.0.0" +yauzl@2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + +yauzl@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= + dependencies: + fd-slicer "~1.0.1" + zwitch@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.4.tgz#93b1b993b13c8926753a41afaf8f27bbfac6be8b"