diff --git a/app/client/.dockerignore b/app/client/.dockerignore new file mode 100644 index 0000000000..4376f41539 --- /dev/null +++ b/app/client/.dockerignore @@ -0,0 +1,5 @@ +.git +.idea +node_modules +build +build.tgz \ No newline at end of file diff --git a/app/client/.gitignore b/app/client/.gitignore index ec9dcd5bd9..33be47b5f7 100755 --- a/app/client/.gitignore +++ b/app/client/.gitignore @@ -28,3 +28,5 @@ yarn-error.log* /src/assets/icons/fonts/* .idea .storybook-out/ +cypress/videos +results/ \ No newline at end of file diff --git a/app/client/.gitlab-ci.yml b/app/client/.gitlab-ci.yml new file mode 100644 index 0000000000..7d38185c39 --- /dev/null +++ b/app/client/.gitlab-ci.yml @@ -0,0 +1,83 @@ +.only-default: &only-default + only: + - release + - master + - merge_requests + +image: cypress/base:10.16.3 + +variables: + npm_config_cache: "$CI_PROJECT_DIR/.npm" + CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/Cypress" + DOCKER_DRIVER: overlay + DOCKER_IMAGE_NAME: $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_SHORT_SHA + +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - .npm + - cache/Cypress + - node_modules + +stages: + - build + - test + - package + - deploy + +react-build: + stage: build + script: + - yarn install + # show where the Cypress test runner binaries are cached + - $(npm bin)/cypress cache path + # show all installed versions of Cypress binary + - $(npm bin)/cypress cache list + - $(npm bin)/cypress verify + only: + - release + - merge_requests + +cypress-test: + stage: test + script: + - REACT_APP_ENVIRONMENT=DEVELOPMENT REACT_APP_BASE_URL="https://release-api.appsmith.com" GIT_SHA=$CI_COMMIT_SHORT_SHA yarn build + - yarn global add serve + - serve -s build -p 3000 & + # This is required in order to ensure that all the test cases pass + - echo "127.0.0.1 dev.appsmith.com" >> /etc/hosts + - yarn test + artifacts: + expire_in: 1 week + paths: + - build/ + - cypress/screenshots + - cypress/videos + only: + # We don't test on master right now because of changing environment variables for REACT_APP_BASE_URL. Need to figure out a way to configure that. + - release + - merge_requests + +docker-package-release: + image: docker:dind + services: + - docker:dind + stage: package + script: + - docker build --build-arg REACT_APP_ENVIRONMENT=STAGING --build-arg GIT_SHA=$CI_COMMIT_SHORT_SHA -t appsmith/appsmith-editor:release . + - docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_ACCESS_TOKEN + - docker push appsmith/appsmith-editor:release + only: + - release + +docker-package-prod: + image: docker:dind + services: + - docker:dind + stage: package + script: + - docker build --build-arg REACT_APP_ENVIRONMENT=PRODUCTION --build-arg GIT_SHA=$CI_COMMIT_SHORT_SHA -t appsmith/appsmith-editor:latest . + - docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_ACCESS_TOKEN + - docker push appsmith/appsmith-editor:latest + only: + - master diff --git a/app/client/Dockerfile b/app/client/Dockerfile new file mode 100644 index 0000000000..92c0e97200 --- /dev/null +++ b/app/client/Dockerfile @@ -0,0 +1,22 @@ +FROM node:10.19-alpine as build-deps + +WORKDIR /usr/src/app + +ARG REACT_APP_ENVIRONMENT="DEVELOPMENT" +ARG GIT_SHA="" + +ENV REACT_APP_ENVIRONMENT=${REACT_APP_ENVIRONMENT} +ENV REACT_APP_BASE_URL=${REACT_APP_BASE_URL} +ENV GIT_SHA=${GIT_SHA} + +COPY package.json yarn.lock ./ +COPY . ./ +RUN yarn install && yarn build + +# Use the output from the previous Docker build to create the nginx container +FROM nginx:1.17.9-alpine as final-image +COPY --from=build-deps /usr/src/app/docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build-deps /usr/src/app/build /var/www/appsmith + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/app/client/build.sh b/app/client/build.sh index f924248d5f..c71f8d9d7a 100755 --- a/app/client/build.sh +++ b/app/client/build.sh @@ -1,7 +1,6 @@ - #!/bin/sh -GIT_SHA=$(eval git rev-parse HEAD) -GIT_BRANCH=$(git branch --no-color | grep -E '^\*' | sed 's/\*[^a-z]*//g') +# GIT_SHA=$(eval git rev-parse HEAD) +# GIT_BRANCH=$(git branch --no-color | grep -E '^\*' | sed 's/\*[^a-z]*//g') # RELEASE="${GIT_BRANCH}_${GIT_SHA}" @@ -9,7 +8,7 @@ GIT_BRANCH=$(git branch --no-color | grep -E '^\*' | sed 's/\*[^a-z]*//g') # RELEASE=$(echo "$RELEASE" | sed -e 's/[\/\\\ .]/\-/g') # echo $RELEASE -REACT_APP_SENTRY_RELEASE=$GIT_SHA craco --max-old-space-size=2048 build --config craco.build.config.js +REACT_APP_SENTRY_RELEASE=$GIT_SHA craco --max-old-space-size=4096 build --config craco.build.config.js -rm $PWD/build/static/js/*.js.map +rm ./build/static/js/*.js.map echo "build finished" \ No newline at end of file diff --git a/app/client/cypress.json b/app/client/cypress.json index 0967ef424b..3c75281b43 100644 --- a/app/client/cypress.json +++ b/app/client/cypress.json @@ -1 +1,11 @@ -{} +{ + "baseUrl":"http://dev.appsmith.com:3000/", + + "reporter": "mochawesome", + "reporterOptions": { + "reportDir": "results", + "overwrite": false, + "html": false, + "json": true + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/user.json b/app/client/cypress/fixtures/user.json new file mode 100644 index 0000000000..9d5a67368d --- /dev/null +++ b/app/client/cypress/fixtures/user.json @@ -0,0 +1,6 @@ + + { + "username": "testowner@appsmith.com", + "password": "own3rT3st1ng", + + } \ No newline at end of file diff --git a/app/client/cypress/integration/OnBoarding/Login_spec.js b/app/client/cypress/integration/OnBoarding/Login_spec.js new file mode 100644 index 0000000000..ba0cb7ea2c --- /dev/null +++ b/app/client/cypress/integration/OnBoarding/Login_spec.js @@ -0,0 +1,20 @@ +var loginPage= require('../../locators/LoginPage.json') +const loginData=require('../../fixtures/user.json') + +context('Cypress test',function() { + +it('Login functionality',function(){ + + cy.LogintoApp(loginData.username,loginData.password) + cy.get('input[type="text"]').type('Test app') + cy.wait(3000) + cy.get('.t--application-edit-link').click() + cy.wait(5000) + cy.get('.t--draggable-buttonwidget').click({ force: true }) + cy.wait(2000) + cy.get('textarea').first().click({ force: true }).clear({ force: true }).type('Test', { force: true }) + cy.wait(5000) + cy.get('.t--application-publish-btn').click() + +}) +}) \ 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 deleted file mode 100644 index 03eba5e333..0000000000 --- a/app/client/cypress/integration/examples/actions.spec.js +++ /dev/null @@ -1,309 +0,0 @@ -/// - -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 deleted file mode 100644 index 9421d628c1..0000000000 --- a/app/client/cypress/integration/examples/aliasing.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -/// - -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 deleted file mode 100644 index 85541b4d5b..0000000000 --- a/app/client/cypress/integration/examples/assertions.spec.js +++ /dev/null @@ -1,168 +0,0 @@ -/// - -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 deleted file mode 100644 index 4b7a7699fd..0000000000 --- a/app/client/cypress/integration/examples/connectors.spec.js +++ /dev/null @@ -1,96 +0,0 @@ -/// - -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 deleted file mode 100644 index ce35df8f88..0000000000 --- a/app/client/cypress/integration/examples/cookies.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -/// - -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 deleted file mode 100644 index 54b1364ec3..0000000000 --- a/app/client/cypress/integration/examples/cypress_api.spec.js +++ /dev/null @@ -1,228 +0,0 @@ -/// - -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 deleted file mode 100644 index 7eaed162c7..0000000000 --- a/app/client/cypress/integration/examples/files.spec.js +++ /dev/null @@ -1,119 +0,0 @@ -/// - -/// 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 deleted file mode 100644 index 6826bbf506..0000000000 --- a/app/client/cypress/integration/examples/local_storage.spec.js +++ /dev/null @@ -1,58 +0,0 @@ -/// - -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 deleted file mode 100644 index 67e68e1ceb..0000000000 --- a/app/client/cypress/integration/examples/location.spec.js +++ /dev/null @@ -1,34 +0,0 @@ -/// - -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 deleted file mode 100644 index bc8af442f2..0000000000 --- a/app/client/cypress/integration/examples/misc.spec.js +++ /dev/null @@ -1,93 +0,0 @@ -/// - -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 deleted file mode 100644 index bfb05ef29e..0000000000 --- a/app/client/cypress/integration/examples/navigation.spec.js +++ /dev/null @@ -1,60 +0,0 @@ -/// - -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 deleted file mode 100644 index c96cbb228f..0000000000 --- a/app/client/cypress/integration/examples/network_requests.spec.js +++ /dev/null @@ -1,217 +0,0 @@ -/// - -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 deleted file mode 100644 index 897298a443..0000000000 --- a/app/client/cypress/integration/examples/querying.spec.js +++ /dev/null @@ -1,114 +0,0 @@ -/// - -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 deleted file mode 100644 index 53e84a08d5..0000000000 --- a/app/client/cypress/integration/examples/spies_stubs_clocks.spec.js +++ /dev/null @@ -1,98 +0,0 @@ -/// - -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 deleted file mode 100644 index 9108b0efbb..0000000000 --- a/app/client/cypress/integration/examples/traversal.spec.js +++ /dev/null @@ -1,140 +0,0 @@ -/// - -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 deleted file mode 100644 index e69a96406e..0000000000 --- a/app/client/cypress/integration/examples/utilities.spec.js +++ /dev/null @@ -1,145 +0,0 @@ -/// - -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 deleted file mode 100644 index 1df56b0af5..0000000000 --- a/app/client/cypress/integration/examples/viewport.spec.js +++ /dev/null @@ -1,63 +0,0 @@ -/// - -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 deleted file mode 100644 index 108db7cbb0..0000000000 --- a/app/client/cypress/integration/examples/waiting.spec.js +++ /dev/null @@ -1,35 +0,0 @@ -/// - -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 deleted file mode 100644 index 2d6d7fc268..0000000000 --- a/app/client/cypress/integration/examples/window.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -/// - -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/locators/LoginPage.json b/app/client/cypress/locators/LoginPage.json new file mode 100644 index 0000000000..d212984269 --- /dev/null +++ b/app/client/cypress/locators/LoginPage.json @@ -0,0 +1,5 @@ +{ + "username":"input[name='username']", + "password":"input[name='password']", + "submitBtn":"button[type='submit']" +} \ No newline at end of file diff --git a/app/client/cypress/plugins/index.js b/app/client/cypress/plugins/index.js index dffed2532f..aa9918d215 100644 --- a/app/client/cypress/plugins/index.js +++ b/app/client/cypress/plugins/index.js @@ -1,3 +1,4 @@ +/// // *********************************************************** // This example plugins/index.js can be used to load plugins // @@ -11,7 +12,10 @@ // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) +/** + * @type {Cypress.PluginConfig} + */ module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config -}; +} diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index ca4d256f3e..3caea750bd 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1,25 +1,9 @@ -// *********************************************** -// 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) => { ... }) +var loginPage= require('../locators/LoginPage.json') + +Cypress.Commands.add("LogintoApp",(uname,pword)=>{ + cy.visit('/') + cy.wait(6000) + cy.get(loginPage.username).type(uname) + cy.get(loginPage.password).type(pword) + cy.get(loginPage.submitBtn).click() +}) \ No newline at end of file diff --git a/app/client/cypress/test.sh b/app/client/cypress/test.sh new file mode 100755 index 0000000000..4e6f4ad9af --- /dev/null +++ b/app/client/cypress/test.sh @@ -0,0 +1 @@ +$(npm bin)/cypress run CYPRESS_BASE_URL='http://dev.appsmith.com:3000/' --spec "cypress/integration/OnBoarding/Login_spec.js" \ No newline at end of file diff --git a/app/client/docker/nginx.conf b/app/client/docker/nginx.conf new file mode 100644 index 0000000000..57c2823ec8 --- /dev/null +++ b/app/client/docker/nginx.conf @@ -0,0 +1,46 @@ +server { + listen 80; + server_name localhost; + gzip on; + + #charset koi8-r; + #access_log /var/log/nginx/host.access.log main; + root /var/www/appsmith; + index index.html index.htm; + + location / { + try_files $uri /index.html =404; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /var/www/appsmith; + } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} +} diff --git a/app/client/package.json b/app/client/package.json index 56bfc0df29..c2e6f53719 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -94,8 +94,8 @@ "scripts": { "analyze": "source-map-explorer 'build/static/js/*.js'", "start": "REACT_APP_BASE_URL=https://release-api.appsmith.com REACT_APP_ENVIRONMENT=DEVELOPMENT HOST=dev.appsmith.com craco start", - "build": "$PWD/build.sh", - "test": "CI=true craco test", + "build": "./build.sh", + "test": "cypress/test.sh", "eject": "react-scripts eject", "start-prod": "REACT_APP_BASE_URL=https://api.appsmith.com REACT_APP_ENVIRONMENT=PRODUCTION craco start", "storybook": "start-storybook", @@ -135,6 +135,7 @@ "@typescript-eslint/parser": "^2.0.0", "babel-loader": "^8.0.6", "cypress": "^3.7.0", + "cypress-multi-reporters": "^1.2.4", "dotenv": "^8.1.0", "eslint": "^6.4.0", "eslint-config-prettier": "^6.1.0", @@ -142,6 +143,10 @@ "eslint-plugin-prettier": "^3.1.0", "eslint-plugin-react": "^7.14.3", "eslint-plugin-react-hooks": "^2.3.0", + "mocha": "^7.1.0", + "mocha-junit-reporter": "^1.23.3", + "mochawesome": "^5.0.0", + "mochawesome-report-generator": "^4.1.0", "react-docgen-typescript-loader": "^3.6.0", "react-is": "^16.12.0", "react-test-renderer": "^16.11.0", diff --git a/app/client/results/mochawesome.json b/app/client/results/mochawesome.json new file mode 100644 index 0000000000..0b9931dac0 --- /dev/null +++ b/app/client/results/mochawesome.json @@ -0,0 +1,105 @@ +{ + "stats": { + "suites": 1, + "tests": 1, + "passes": 1, + "pending": 0, + "failures": 0, + "start": "2020-03-06T06:36:05.120Z", + "end": "2020-03-06T06:36:43.306Z", + "duration": 38186, + "testsRegistered": 1, + "passPercent": 100, + "pendingPercent": 0, + "other": 0, + "hasOther": false, + "skipped": 0, + "hasSkipped": false + }, + "results": [ + { + "uuid": "ec59b4ec-37da-4565-825f-b4815a3dfbd6", + "title": "", + "fullFile": "cypress\\integration\\OnBoarding\\Login_spec.js", + "file": "cypress\\integration\\OnBoarding\\Login_spec.js", + "beforeHooks": [], + "afterHooks": [], + "tests": [], + "suites": [ + { + "uuid": "352aa634-cc6a-4b94-a333-cb87b4ea1e1b", + "title": "Cypress test", + "fullFile": "", + "file": "", + "beforeHooks": [], + "afterHooks": [], + "tests": [ + { + "title": "Login functionality", + "fullTitle": "Cypress test Login functionality", + "timedOut": null, + "duration": 38059, + "state": "passed", + "speed": "slow", + "pass": true, + "fail": false, + "pending": false, + "context": null, + "code": "cy.LogintoApp(loginData.username, loginData.password);\ncy.get('input[type=\"text\"]').type('Test app');\ncy.wait(3000);\ncy.get('.t--application-edit-link').click();\ncy.wait(5000);\ncy.get('.t--draggable-buttonwidget').click({\n force: true\n});\ncy.wait(2000);\ncy.get('textarea').first().click({\n force: true\n}).clear({\n force: true\n}).type('Test', {\n force: true\n});\ncy.wait(5000);\ncy.get('.t--application-publish-btn').click();", + "err": {}, + "uuid": "4a5df4d2-4368-4506-9c98-90d44b9aa355", + "parentUUID": "352aa634-cc6a-4b94-a333-cb87b4ea1e1b", + "isHook": false, + "skipped": false + } + ], + "suites": [], + "passes": [ + "4a5df4d2-4368-4506-9c98-90d44b9aa355" + ], + "failures": [], + "pending": [], + "skipped": [], + "duration": 38059, + "root": false, + "rootEmpty": false, + "_timeout": 2000 + } + ], + "passes": [], + "failures": [], + "pending": [], + "skipped": [], + "duration": 0, + "root": true, + "rootEmpty": true, + "_timeout": 2000 + } + ], + "meta": { + "mocha": { + "version": "7.1.0" + }, + "mochawesome": { + "options": { + "quiet": false, + "reportFilename": "mochawesome", + "saveHtml": false, + "saveJson": true, + "consoleReporter": "spec", + "useInlineDiffs": false, + "code": true + }, + "version": "5.0.0" + }, + "marge": { + "options": { + "reportDir": "results", + "overwrite": false, + "html": false, + "json": true + }, + "version": "4.1.0" + } + } +} \ No newline at end of file diff --git a/app/client/src/components/designSystems/appsmith/FilePickerComponent.tsx b/app/client/src/components/designSystems/appsmith/FilePickerComponent.tsx index 4a58ff53b5..0665f0d672 100644 --- a/app/client/src/components/designSystems/appsmith/FilePickerComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/FilePickerComponent.tsx @@ -23,7 +23,7 @@ class FilePickerComponent extends React.Component< }; render() { - let label = "Select files"; + let label = this.props.label; if (this.props.files && this.props.files.length) { label = `${this.props.files.length} files selected`; } diff --git a/app/client/src/components/editorComponents/DraggableComponent.tsx b/app/client/src/components/editorComponents/DraggableComponent.tsx index 079d5a9467..a4aba58481 100644 --- a/app/client/src/components/editorComponents/DraggableComponent.tsx +++ b/app/client/src/components/editorComponents/DraggableComponent.tsx @@ -167,12 +167,14 @@ const DraggableComponent = (props: DraggableComponentProps) => { setIsDragging && setIsDragging(true); }, end: (widget, monitor) => { - if (monitor.didDrop()) { + const didDrop = monitor.didDrop(); + if (didDrop) { showPropertyPane && showPropertyPane(props.widgetId, true); } AnalyticsUtil.logEvent("WIDGET_DROP", { widgetName: props.widgetName, widgetType: props.type, + didDrop: didDrop, }); // Take this to the bottom of the stack. So that it runs last. setTimeout(() => setIsDragging && setIsDragging(false), 0); diff --git a/app/client/src/components/editorComponents/Sidebar.tsx b/app/client/src/components/editorComponents/Sidebar.tsx index 7875173666..656c996fd9 100644 --- a/app/client/src/components/editorComponents/Sidebar.tsx +++ b/app/client/src/components/editorComponents/Sidebar.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Switch, Route } from "react-router"; +import { Switch } from "react-router"; import styled from "styled-components"; import { API_EDITOR_URL, @@ -10,6 +10,7 @@ import { import WidgetSidebar from "pages/Editor/WidgetSidebar"; import ApiSidebar from "pages/Editor/ApiSidebar"; import PageListSidebar from "pages/Editor/PageListSidebar"; +import AppRoute from "pages/common/AppRoute"; const SidebarWrapper = styled.div` background-color: ${props => props.theme.colors.paneBG}; @@ -22,13 +23,29 @@ export const Sidebar = () => { return ( - - - - + + + diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index 6cb4f59c1f..e9dc7cd424 100755 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -5,14 +5,14 @@ import { Provider } from "react-redux"; import Loader from "pages/common/Loader"; import "./index.css"; import * as serviceWorker from "./serviceWorker"; -import { Router, Route, Switch, Redirect } from "react-router-dom"; +import { Router, Switch, Redirect } from "react-router-dom"; import history from "./utils/history"; import { ThemeProvider, theme } from "constants/DefaultTheme"; import { DndProvider } from "react-dnd"; import TouchBackend from "react-dnd-touch-backend"; import { appInitializer } from "utils/AppsmithUtils"; -import ProtectedRoute from "./pages/common/ProtectedRoute"; +import AppRoute from "./pages/common/AppRoute"; import { Slide, ToastContainer } from "react-toastify"; import store from "./store"; import { @@ -64,20 +64,54 @@ ReactDOM.render( - - - - + + + + - - - - + + + diff --git a/app/client/src/pages/AppViewer/AppPage.tsx b/app/client/src/pages/AppViewer/AppPage.tsx index eb342c34a7..4710ad404b 100644 --- a/app/client/src/pages/AppViewer/AppPage.tsx +++ b/app/client/src/pages/AppViewer/AppPage.tsx @@ -1,9 +1,10 @@ -import React from "react"; +import React, { useEffect } from "react"; import styled from "styled-components"; import { WidgetProps } from "widgets/BaseWidget"; import { RenderModes } from "constants/WidgetConstants"; import WidgetFactory from "utils/WidgetFactory"; import { ContainerWidgetProps } from "widgets/ContainerWidget"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const PageView = styled.div<{ width: number }>` height: 100%; @@ -14,9 +15,18 @@ const PageView = styled.div<{ width: number }>` type AppPageProps = { dsl: ContainerWidgetProps; + pageName?: string; + pageId?: string; }; export const AppPage = (props: AppPageProps) => { + useEffect(() => { + AnalyticsUtil.logEvent("PAGE_LOAD", { + pageName: props.pageName, + pageId: props.pageId, + mode: "VIEW", + }); + }, [props.pageId, props.pageName]); return ( {props.dsl.widgetId && diff --git a/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx b/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx index 2ae6d97476..4f6680472f 100644 --- a/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx +++ b/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx @@ -12,7 +12,10 @@ import { theme } from "constants/DefaultTheme"; import { NonIdealState, Icon, Spinner } from "@blueprintjs/core"; import Centered from "components/designSystems/appsmith/CenteredWrapper"; import AppPage from "./AppPage"; -import { getCanvasWidgetDsl } from "selectors/editorSelectors"; +import { + getCanvasWidgetDsl, + getCurrentPageName, +} from "selectors/editorSelectors"; const Section = styled.section` background: ${props => props.theme.colors.bodyBG}; @@ -25,6 +28,7 @@ const Section = styled.section` type AppViewerPageContainerProps = { isFetchingPage: boolean; widgets?: ContainerWidgetProps; + currentPageName?: string; fetchPage: (pageId: string) => void; } & RouteComponentProps; @@ -77,7 +81,11 @@ class AppViewerPageContainer extends Component { } else if (!this.props.isFetchingPage && this.props.widgets) { return ( - + ); } @@ -87,6 +95,7 @@ class AppViewerPageContainer extends Component { const mapStateToProps = (state: AppState) => ({ isFetchingPage: getIsFetchingPage(state), widgets: getCanvasWidgetDsl(state), + currentPageName: getCurrentPageName(state), }); const mapDispatchToProps = (dispatch: any) => ({ diff --git a/app/client/src/pages/AppViewer/index.tsx b/app/client/src/pages/AppViewer/index.tsx index 381bf4ae8f..c66c57da65 100644 --- a/app/client/src/pages/AppViewer/index.tsx +++ b/app/client/src/pages/AppViewer/index.tsx @@ -2,7 +2,7 @@ import React, { Component } from "react"; import styled from "styled-components"; import { connect } from "react-redux"; import { withRouter, RouteComponentProps } from "react-router"; -import { Switch, Route } from "react-router-dom"; +import { Switch } from "react-router-dom"; import { AppState } from "reducers"; import { AppViewerRouteParams, @@ -32,6 +32,7 @@ import { resetChildrenMetaProperty, updateWidgetMetaProperty, } from "actions/metaActions"; +import AppRoute from "pages/common/AppRoute"; const AppViewWrapper = styled.div` margin-top: ${props => props.theme.headerHeight}; @@ -107,10 +108,12 @@ class AppViewer extends Component< - diff --git a/app/client/src/pages/Editor/APIEditor/index.tsx b/app/client/src/pages/Editor/APIEditor/index.tsx index ef49e1bad2..a4e4a9a0ef 100644 --- a/app/client/src/pages/Editor/APIEditor/index.tsx +++ b/app/client/src/pages/Editor/APIEditor/index.tsx @@ -20,6 +20,8 @@ import _ from "lodash"; import { getPluginIdOfName } from "selectors/entitiesSelector"; import { getCurrentApplication } from "selectors/applicationSelectors"; import { UserApplication } from "constants/userConstants"; +import AnalyticsUtil from "utils/AnalyticsUtil"; +import { getCurrentPageName } from "selectors/editorSelectors"; interface ReduxStateProps { actions: ActionDataState; @@ -27,6 +29,8 @@ interface ReduxStateProps { formData: RestAction; pluginId: string | undefined; currentApplication: UserApplication; + currentPageName: string | undefined; + pages: any; } interface ReduxActionProps { submitForm: (name: string) => void; @@ -36,6 +40,11 @@ interface ReduxActionProps { updateAction: (data: RestAction) => void; } +function getPageName(pages: any, pageId: string) { + const page = pages.find((page: any) => page.pageId === pageId); + return page ? page.pageName : ""; +} + type Props = ReduxActionProps & ReduxStateProps & RouteComponentProps<{ apiId: string; applicationId: string; pageId: string }>; @@ -55,15 +64,33 @@ class ApiEditor extends React.Component { }; handleSaveClick = () => { + const pageName = getPageName(this.props.pages, this.props.formData.pageId); + AnalyticsUtil.logEvent("SAVE_API_CLICK", { + apiName: this.props.formData.name, + apiID: this.props.match.params.apiId, + pageName: pageName, + }); this.props.submitForm(API_EDITOR_FORM_NAME); }; handleDeleteClick = () => { + const pageName = getPageName(this.props.pages, this.props.formData.pageId); + AnalyticsUtil.logEvent("DELETE_API_CLICK", { + apiName: this.props.formData.name, + apiID: this.props.match.params.apiId, + pageName: pageName, + }); this.props.deleteAction( this.props.match.params.apiId, this.props.formData.name, ); }; handleRunClick = (paginationField?: PaginationField) => { + const pageName = getPageName(this.props.pages, this.props.formData.pageId); + AnalyticsUtil.logEvent("RUN_API_CLICK", { + apiName: this.props.formData.name, + apiID: this.props.match.params.apiId, + pageName: pageName, + }); this.props.runAction(this.props.match.params.apiId, paginationField); }; @@ -123,6 +150,8 @@ const mapStateToProps = (state: AppState): ReduxStateProps => ({ actions: state.entities.actions, apiPane: state.ui.apiPane, currentApplication: getCurrentApplication(state), + currentPageName: getCurrentPageName(state), + pages: state.entities.pageList.pages, formData: getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction, }); diff --git a/app/client/src/pages/Editor/ApiSidebar.tsx b/app/client/src/pages/Editor/ApiSidebar.tsx index 27774017f9..4b588c9bf5 100644 --- a/app/client/src/pages/Editor/ApiSidebar.tsx +++ b/app/client/src/pages/Editor/ApiSidebar.tsx @@ -138,7 +138,12 @@ class ApiSidebar extends React.Component { this.props.copyAction(itemId, destinationPageId, name); }; - handleDelete = (itemId: string, itemName: string) => { + handleDelete = (itemId: string, itemName: string, pageName: string) => { + AnalyticsUtil.logEvent("DELETE_API_CLICK", { + apiId: itemId, + apiName: itemName, + pageName: pageName, + }); this.props.deleteAction(itemId, itemName); }; diff --git a/app/client/src/pages/Editor/EditorHeader.tsx b/app/client/src/pages/Editor/EditorHeader.tsx index 99049197d2..b02d63b21b 100644 --- a/app/client/src/pages/Editor/EditorHeader.tsx +++ b/app/client/src/pages/Editor/EditorHeader.tsx @@ -25,6 +25,7 @@ import { DropdownOnSelectActions, getOnSelectAction, } from "pages/common/CustomizedDropdown/dropdownHelpers"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const LoadingContainer = styled.div` display: flex; @@ -106,10 +107,16 @@ export const EditorHeader = (props: EditorHeaderProps) => { ); return { content: page.pageName, - onSelect: () => + onSelect: () => { + AnalyticsUtil.logEvent("PAGE_SWITCH", { + pageName: page.pageName, + pageId: page.pageId, + mode: "EDIT", + }); getOnSelectAction(DropdownOnSelectActions.REDIRECT, { path: url, - }), + }); + }, shouldCloseDropdown: true, active: page.pageId === props.currentPageId, }; diff --git a/app/client/src/pages/Editor/EditorSidebar.tsx b/app/client/src/pages/Editor/EditorSidebar.tsx index f2bf09a37c..829b371878 100644 --- a/app/client/src/pages/Editor/EditorSidebar.tsx +++ b/app/client/src/pages/Editor/EditorSidebar.tsx @@ -159,7 +159,7 @@ type EditorSidebarComponentProps = { onItemSelected: (itemId: string) => void; moveItem: (itemId: string, destinationPageId: string) => void; copyItem: (itemId: string, destinationPageId: string) => void; - deleteItem: (itemId: string, itemName: string) => void; + deleteItem: (itemId: string, itemName: string, pageName: string) => void; }; type Props = ReduxStateProps & @@ -390,6 +390,7 @@ class EditorSidebar extends React.Component { this.props.deleteItem( item.id, item.name, + page.name, ), label: "Delete", intent: "danger", diff --git a/app/client/src/pages/Editor/WidgetCard.tsx b/app/client/src/pages/Editor/WidgetCard.tsx index 8228c89249..6451a20259 100644 --- a/app/client/src/pages/Editor/WidgetCard.tsx +++ b/app/client/src/pages/Editor/WidgetCard.tsx @@ -76,10 +76,11 @@ const WidgetCard = (props: CardProps) => { showPropertyPane && showPropertyPane(undefined); setIsDragging && setIsDragging(true); }, - end: () => { + end: (widget, monitor) => { AnalyticsUtil.logEvent("WIDGET_CARD_DROP", { widgetType: props.details.type, widgetName: props.details.widgetCardName, + didDrop: monitor.didDrop(), }); setIsDragging && setIsDragging(false); }, diff --git a/app/client/src/pages/Editor/WidgetsEditor.tsx b/app/client/src/pages/Editor/WidgetsEditor.tsx index 02b8ffb028..fc749629d8 100644 --- a/app/client/src/pages/Editor/WidgetsEditor.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor.tsx @@ -10,6 +10,7 @@ import { getIsFetchingPage, getCurrentPageId, getCanvasWidgetDsl, + getCurrentPageName, } from "selectors/editorSelectors"; import { ContainerWidgetProps } from "widgets/ContainerWidget"; import { BuilderRouteParams } from "constants/routes"; @@ -17,6 +18,7 @@ import Centered from "components/designSystems/appsmith/CenteredWrapper"; import EditorContextProvider from "components/editorComponents/EditorContextProvider"; import { Spinner } from "@blueprintjs/core"; import { useWidgetSelection } from "utils/hooks/dragResizeHooks"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const EditorWrapper = styled.div` display: flex; @@ -48,6 +50,7 @@ type EditorProps = { fetchPage: (pageId: string, width?: number) => void; currentPageId?: string; isFetchingPage: boolean; + currentPageName?: string; }; const WidgetsEditor = (props: EditorProps) => { @@ -55,6 +58,19 @@ const WidgetsEditor = (props: EditorProps) => { const { focusWidget, selectWidget } = useWidgetSelection(); const { pageId } = params; + useEffect(() => { + if ( + props.currentPageName !== undefined && + props.currentPageId !== undefined + ) { + AnalyticsUtil.logEvent("PAGE_LOAD", { + pageName: props.currentPageName, + pageId: props.currentPageId, + mode: "EDIT", + }); + } + }, [props.currentPageName, props.currentPageId]); + const handleWrapperClick = () => { focusWidget && focusWidget(); selectWidget && selectWidget(); @@ -92,6 +108,7 @@ const mapStateToProps = (state: AppState) => { widgets: getCanvasWidgetDsl(state), isFetchingPage: getIsFetchingPage(state), currentPageId: getCurrentPageId(state), + currentPageName: getCurrentPageName(state), }; }; diff --git a/app/client/src/pages/Editor/routes.tsx b/app/client/src/pages/Editor/routes.tsx index a2c4d715ec..bbd1194924 100644 --- a/app/client/src/pages/Editor/routes.tsx +++ b/app/client/src/pages/Editor/routes.tsx @@ -1,10 +1,5 @@ import React from "react"; -import { - Route, - Switch, - withRouter, - RouteComponentProps, -} from "react-router-dom"; +import { Switch, withRouter, RouteComponentProps } from "react-router-dom"; import ApiEditor from "./APIEditor"; import { API_EDITOR_ID_URL, @@ -15,6 +10,7 @@ import { APIEditorRouteParams, } from "constants/routes"; import styled from "styled-components"; +import AppRoute from "pages/common/AppRoute"; const Wrapper = styled.div<{ isVisible: boolean; showOnlySidebar?: boolean }>` position: absolute; @@ -105,8 +101,18 @@ class EditorsRouter extends React.Component< onClick={this.preventClose} > - - + + diff --git a/app/client/src/pages/UserAuth/Login.tsx b/app/client/src/pages/UserAuth/Login.tsx index b934e885a1..9cec17d281 100644 --- a/app/client/src/pages/UserAuth/Login.tsx +++ b/app/client/src/pages/UserAuth/Login.tsx @@ -48,6 +48,7 @@ import { AuthCardNavLink, AuthCardBody, } from "./StyledComponents"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const validate = (values: LoginFormValues) => { const errors: LoginFormValues = {}; @@ -143,11 +144,17 @@ export const Login = (props: LoginFormProps) => { intent="primary" filled size="large" + onClick={() => { + AnalyticsUtil.logEvent("LOGIN_CLICK", { + loginMethod: "EMAIL", + }); + }} /> diff --git a/app/client/src/pages/UserAuth/SignUp.tsx b/app/client/src/pages/UserAuth/SignUp.tsx index 389981cc6d..59de7537cc 100644 --- a/app/client/src/pages/UserAuth/SignUp.tsx +++ b/app/client/src/pages/UserAuth/SignUp.tsx @@ -41,6 +41,7 @@ import Button from "components/editorComponents/Button"; import { isEmail, isStrongPassword, isEmptyString } from "utils/formhelpers"; import { signupFormSubmitHandler, SignupFormValues } from "./helpers"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const validate = (values: SignupFormValues) => { const errors: SignupFormValues = {}; @@ -119,12 +120,18 @@ export const SignUp = (props: InjectedFormProps) => { intent="primary" filled size="large" + onClick={() => { + AnalyticsUtil.logEvent("SIGNUP_CLICK", { + signupMethod: "EMAIL", + }); + }} /> diff --git a/app/client/src/pages/UserAuth/ThirdPartyAuth.tsx b/app/client/src/pages/UserAuth/ThirdPartyAuth.tsx index cd84cace54..847198ad1f 100644 --- a/app/client/src/pages/UserAuth/ThirdPartyAuth.tsx +++ b/app/client/src/pages/UserAuth/ThirdPartyAuth.tsx @@ -5,6 +5,7 @@ import { SocialLoginType, } from "constants/SocialLogin"; import { IntentColors, getBorderCSSShorthand } from "constants/DefaultTheme"; +import AnalyticsUtil, { EventName } from "utils/AnalyticsUtil"; const ThirdPartyAuthWrapper = styled.div` display: flex; @@ -60,13 +61,27 @@ export const SocialLoginTypes: Record = { GITHUB: "github", }; +type SignInType = "SIGNIN" | "SIGNUP"; + const SocialLoginButton = (props: { logo: string; name: string; url: string; + type: SignInType; }) => { return ( - + { + let eventName: EventName = "LOGIN_CLICK"; + if (props.type === "SIGNUP") { + eventName = "SIGNUP_CLICK"; + } + AnalyticsUtil.logEvent(eventName, { + loginMethod: props.name.toUpperCase(), + }); + }} + > @@ -75,10 +90,19 @@ const SocialLoginButton = (props: { ); }; -export const ThirdPartyAuth = (props: { logins: SocialLoginType[] }) => { +export const ThirdPartyAuth = (props: { + logins: SocialLoginType[]; + type: SignInType; +}) => { const socialLoginButtons = getSocialLoginButtonProps(props.logins).map( item => { - return ; + return ( + + ); }, ); return {socialLoginButtons}; diff --git a/app/client/src/pages/UserAuth/index.tsx b/app/client/src/pages/UserAuth/index.tsx index 98d24251f2..d648aec96c 100644 --- a/app/client/src/pages/UserAuth/index.tsx +++ b/app/client/src/pages/UserAuth/index.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Switch, Route, useRouteMatch, useLocation } from "react-router-dom"; +import { Switch, useRouteMatch, useLocation } from "react-router-dom"; import Login from "./Login"; import Centered from "components/designSystems/appsmith/CenteredWrapper"; import { animated, useTransition } from "react-spring"; @@ -8,6 +8,7 @@ import SignUp from "./SignUp"; import ForgotPassword from "./ForgotPassword"; import ResetPassword from "./ResetPassword"; import CreatePassword from "./CreatePassword"; +import AppRoute from "pages/common/AppRoute"; const AnimatedAuthCard = animated(AuthContainer); export const UserAuth = () => { const { path } = useRouteMatch(); @@ -24,22 +25,35 @@ export const UserAuth = () => { - - - + + - - diff --git a/app/client/src/pages/common/ProtectedRoute.tsx b/app/client/src/pages/common/AppRoute.tsx similarity index 54% rename from app/client/src/pages/common/ProtectedRoute.tsx rename to app/client/src/pages/common/AppRoute.tsx index 247bbdff55..6ee2e9781b 100644 --- a/app/client/src/pages/common/ProtectedRoute.tsx +++ b/app/client/src/pages/common/AppRoute.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { Route } from "react-router-dom"; import { useDispatch } from "react-redux"; import { useSelector } from "store"; @@ -6,6 +6,7 @@ import { hasAuthExpired } from "utils/storage"; import { User } from "constants/userConstants"; import { setCurrentUserDetails } from "actions/userActions"; import { useShowPropertyPane } from "utils/hooks/dragResizeHooks"; +import AnalyticsUtil from "utils/AnalyticsUtil"; export const checkAuth = (dispatch: any, currentUser?: User) => { return hasAuthExpired().then(hasExpired => { if (!currentUser || hasExpired) { @@ -21,27 +22,44 @@ export const WrappedComponent = (props: any) => { const dispatch = useDispatch(); const currentUser = useSelector(state => state.ui.users.current); checkAuth(dispatch, currentUser); - return currentUser ? props.children : null; + return currentUser || !props.protected ? props.children : null; }; -const ProtectedRoute = ({ +const AppRoute = ({ component: Component, ...rest }: { - path: string; + path?: string; component: React.ReactType; exact?: boolean; + routeProtected?: boolean; + logDisable?: boolean; + name: string; + location?: any; }) => { + useEffect(() => { + if (!rest.logDisable) { + AnalyticsUtil.logEvent("NAVIGATE_EDITOR", { + page: rest.name, + path: rest.location.pathname, + }); + } + }, [rest.name, rest.logDisable, rest.location.pathname]); + return ( ( - - - - )} + render={props => { + return rest.routeProtected ? ( + + + + ) : ( + + ); + }} /> ); }; -export default ProtectedRoute; +export default AppRoute; diff --git a/app/client/src/pages/organization/index.tsx b/app/client/src/pages/organization/index.tsx index 41691dbe55..c819c8c2e7 100644 --- a/app/client/src/pages/organization/index.tsx +++ b/app/client/src/pages/organization/index.tsx @@ -1,18 +1,29 @@ import React from "react"; -import { Switch, Route, useRouteMatch, useLocation } from "react-router-dom"; +import { Switch, useRouteMatch, useLocation } from "react-router-dom"; import PageWrapper from "pages/common/PageWrapper"; import Settings from "./settings"; import Invite from "./invite"; import DefaultOrgPage from "./defaultOrgPage"; +import AppRoute from "pages/common/AppRoute"; export const Organization = () => { const { path } = useRouteMatch(); const location = useLocation(); return ( - - - + + + ); diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index a038feb047..84d9e3dc8a 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -284,10 +284,16 @@ export function* executeActionTriggers( yield call(executeAPIQueryActionSaga, trigger.payload, event); break; case "NAVIGATE_TO": + AnalyticsUtil.logEvent("NAVIGATE", { + pageName: trigger.payload.pageName, + }); yield call(navigateActionSaga, trigger.payload, event); break; case "NAVIGATE_TO_URL": if (trigger.payload.url) { + AnalyticsUtil.logEvent("NAVIGATE", { + navUrl: trigger.payload.url, + }); window.location.href = trigger.payload.url; if (event.callback) event.callback({ success: true }); } else { diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts index 9c32b9fca0..28f6f65dc2 100644 --- a/app/client/src/sagas/InitSagas.ts +++ b/app/client/src/sagas/InitSagas.ts @@ -63,15 +63,6 @@ export function* initializeAppViewerSaga( take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS), ]); - const currentApplication = yield select(getCurrentApplication); - - const appName = currentApplication ? currentApplication.name : ""; - const appId = currentApplication ? currentApplication.id : ""; - AnalyticsUtil.logEvent("PREVIEW_APP", { - appId: appId, - appName: appName, - }); - yield put({ type: ReduxActionTypes.INITIALIZE_PAGE_VIEWER_SUCCESS, }); diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx index 2967b3f507..f9f3d301cc 100644 --- a/app/client/src/utils/AnalyticsUtil.tsx +++ b/app/client/src/utils/AnalyticsUtil.tsx @@ -1,5 +1,7 @@ // Events export type EventName = + | "LOGIN_CLICK" + | "SIGNUP_CLICK" | "PAGE_VIEW" | "ADD_COMPONENT" | "DELETE_COMPONENT" @@ -23,8 +25,11 @@ export type EventName = | "EDITOR_OPEN" | "CREATE_API" | "SAVE_API" + | "SAVE_API_CLICK" | "RUN_API" + | "RUN_API_CLICK" | "DELETE_API" + | "DELETE_API_CLICK" | "DUPLICATE_API" | "MOVE_API" | "API_SELECT" @@ -34,7 +39,10 @@ export type EventName = | "CREATE_APP_CLICK" | "CREATE_APP" | "CREATE_DATA_SOURCE_CLICK" - | "SAVE_DATA_SOURCE"; + | "SAVE_DATA_SOURCE" + | "NAVIGATE" + | "PAGE_LOAD" + | "NAVIGATE_EDITOR"; export type Gender = "MALE" | "FEMALE"; export interface User { @@ -55,7 +63,7 @@ function getApplicationId(location: Location) { } class AnalyticsUtil { - static user: any = {}; + static user: any = undefined; static initializeHotjar(id: string, sv: string) { (function init(h: any, o: any, t: any, j: any, a?: any, r?: any) { h.hj = @@ -137,9 +145,10 @@ class AnalyticsUtil { let finalEventData = eventData; const userData = AnalyticsUtil.user; const appId = getApplicationId(windowDoc.location); - const app = userData.applications.find((app: any) => app.id === appId); - if (userData) { + const app = (userData.applications || []).find( + (app: any) => app.id === appId, + ); finalEventData = { ...finalEventData, userData: { diff --git a/app/client/src/widgets/FilepickerWidget.tsx b/app/client/src/widgets/FilepickerWidget.tsx index ed2956d9ca..d3e132d34d 100644 --- a/app/client/src/widgets/FilepickerWidget.tsx +++ b/app/client/src/widgets/FilepickerWidget.tsx @@ -48,7 +48,7 @@ class FilePickerWidget extends BaseWidget { allowMultipleUploads: true, debug: false, restrictions: { - maxFileSize: props.maxFileSize ? props.maxFileSize * 1000 * 1000 : null, + maxFileSize: props.maxFileSize ? props.maxFileSize * 1024 * 1024 : null, maxNumberOfFiles: props.maxNumFiles, minNumberOfFiles: null, allowedFileTypes: diff --git a/app/client/test/__mocks__/RealmExecutorMock.ts b/app/client/test/__mocks__/RealmExecutorMock.ts deleted file mode 100644 index 96ec32303d..0000000000 --- a/app/client/test/__mocks__/RealmExecutorMock.ts +++ /dev/null @@ -1,19 +0,0 @@ -// __mocks__/RealmExecutor.ts -// Import this named export into your test file: -export const mockExecute = jest.fn().mockImplementation((src, data) => { - let finalSource = "let "; - Object.keys(data).forEach(key => { - finalSource += ` ${key} = ${JSON.stringify(data[key])}, `; - }); - finalSource = finalSource.substring(0, finalSource.length - 2) + ";"; - finalSource += src; - return { result: eval(finalSource), triggers: [] }; -}); - -export const mockRegisterLibrary = jest.fn(); - -// jest.mock("jsExecution/RealmExecutor", () => { -// jest.fn().mockImplementation(() => { -// return { execute: mockExecute, registerLibrary: mockRegisterLibrary }; -// }); -// }); diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 9cb277cf87..336d67ba1b 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -3289,6 +3289,11 @@ ansi-align@^3.0.0: dependencies: string-width "^3.0.0" +ansi-colors@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" + integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== + ansi-colors@^3.0.0: version "3.2.4" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" @@ -4279,6 +4284,11 @@ browser-resolve@^1.11.3: dependencies: resolve "1.1.7" +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -4669,11 +4679,31 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + 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@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" + optionalDependencies: + fsevents "~2.1.1" + chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -5286,6 +5316,11 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -5559,6 +5594,14 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= +cypress-multi-reporters@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-1.2.4.tgz#bf6c95f39f9d2ce210d83e096f452a306bcd49fc" + integrity sha512-JTsF02I2KH1HM+cUEKeJih8EtjFv6jWVrYlRlJAnomwE5UqRQ3M7cAuw+zqBfNSTIvhOzNHtN3LyxomfhycuAQ== + dependencies: + debug "^4.1.1" + lodash "^4.17.11" + cypress@^3.7.0: version "3.8.3" resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.8.3.tgz#e921f5482f1cbe5814891c878f26e704bbffd8f4" @@ -5632,6 +5675,11 @@ date-fns@^1.27.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== +dateformat@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" + integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== + de-indent@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" @@ -5844,6 +5892,16 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -7037,6 +7095,13 @@ flat-cache@^2.0.1: rimraf "2.6.3" write "1.0.3" +flat@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" + integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + dependencies: + is-buffer "~2.0.3" + flatted@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" @@ -7252,7 +7317,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@2.1.2, fsevents@~2.1.2: +fsevents@2.1.2, fsevents@~2.1.1, fsevents@~2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== @@ -7275,6 +7340,11 @@ fstream@^1.0.0, fstream@^1.0.12: mkdirp ">=0.5 0" rimraf "2" +fsu@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fsu/-/fsu-1.1.1.tgz#bd36d3579907c59d85b257a75b836aa9e0c31834" + integrity sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -7440,6 +7510,18 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -7561,6 +7643,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -7751,7 +7838,7 @@ hastscript@^5.0.0: property-information "^5.0.0" space-separated-tokens "^1.0.0" -he@^1.1.0, he@^1.2.0: +he@1.2.0, he@^1.1.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -8347,12 +8434,12 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.5: +is-buffer@^1.0.2, is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@^2.0.0, is-buffer@^2.0.2: +is-buffer@^2.0.0, is-buffer@^2.0.2, is-buffer@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== @@ -9240,7 +9327,7 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@^3.13.1: +js-yaml@3.13.1, js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -9383,7 +9470,7 @@ json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -9793,6 +9880,26 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= + +lodash.isfunction@^3.0.8, lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + +lodash.isobject@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" + integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -9850,6 +9957,13 @@ log-symbols@2.2.0, log-symbols@^2.1.0: dependencies: chalk "^2.0.1" +log-symbols@3.0.0, log-symbols@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== + dependencies: + chalk "^2.4.2" + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -9857,13 +9971,6 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" -log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== - 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" @@ -10030,6 +10137,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +md5@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + mdast-squeeze-paragraphs@^3.0.0: version "3.0.5" resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-3.0.5.tgz#f428b6b944f8faef454db9b58f170c4183cb2e61" @@ -10379,6 +10495,82 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" +mocha-junit-reporter@^1.23.3: + version "1.23.3" + resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.23.3.tgz#941e219dd759ed732f8641e165918aa8b167c981" + integrity sha512-ed8LqbRj1RxZfjt/oC9t12sfrWsjZ3gNnbhV1nuj9R/Jb5/P3Xb4duv2eCfCDMYH+fEu0mqca7m4wsiVjsxsvA== + dependencies: + debug "^2.2.0" + md5 "^2.1.0" + mkdirp "~0.5.1" + strip-ansi "^4.0.0" + xml "^1.0.0" + +mocha@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.0.tgz#c784f579ad0904d29229ad6cb1e2514e4db7d249" + integrity sha512-MymHK8UkU0K15Q/zX7uflZgVoRWiTjy0fXE/QjKts6mowUvGxOdPhZ2qj3b0iZdUrNZlW9LAIMFHB4IW+2b3EQ== + dependencies: + ansi-colors "3.2.3" + browser-stdout "1.3.1" + chokidar "3.3.0" + debug "3.2.6" + diff "3.5.0" + escape-string-regexp "1.0.5" + find-up "3.0.0" + glob "7.1.3" + growl "1.10.5" + he "1.2.0" + js-yaml "3.13.1" + log-symbols "3.0.0" + minimatch "3.0.4" + mkdirp "0.5.1" + ms "2.1.1" + node-environment-flags "1.0.6" + object.assign "4.1.0" + strip-json-comments "2.0.1" + supports-color "6.0.0" + which "1.3.1" + wide-align "1.1.3" + yargs "13.3.0" + yargs-parser "13.1.1" + yargs-unparser "1.6.0" + +mochawesome-report-generator@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mochawesome-report-generator/-/mochawesome-report-generator-4.1.0.tgz#f303e6acb5b30fd900f2ed8a0ba2054a034c9c39" + integrity sha512-8diUnfzLqMPybIsq3aw3Zc4Npw9W2ZCx8/fMR0ShAXfDTtPH4t2mRykXEWBhsBA5+jM74mjWpwEqY6Pmz+pCsw== + dependencies: + chalk "^2.4.2" + dateformat "^3.0.2" + fs-extra "^7.0.0" + fsu "^1.0.2" + lodash.isfunction "^3.0.8" + opener "^1.4.2" + prop-types "^15.7.2" + react "^16.8.5" + react-dom "^16.8.5" + tcomb "^3.2.17" + tcomb-validation "^3.3.0" + validator "^10.11.0" + yargs "^13.2.2" + +mochawesome@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/mochawesome/-/mochawesome-5.0.0.tgz#eb3d96e589ccdd30146bbde581fb3deec01bcbec" + integrity sha512-1Vb0G8rqURptOZUmU5xLkLUWKYlx97IoMF2/xW18tL08Z1CZaUbzLAGbgq/s3DCn/vOrb8Dy7swb/cszp3Ylpg== + dependencies: + chalk "^3.0.0" + diff "^4.0.1" + json-stringify-safe "^5.0.1" + lodash.isempty "^4.4.0" + lodash.isfunction "^3.0.9" + lodash.isobject "^3.0.2" + lodash.isstring "^4.0.1" + mochawesome-report-generator "^4.1.0" + strip-ansi "^6.0.0" + uuid "^7.0.0" + moment-timezone@^0.5.27: version "0.5.27" resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.27.tgz#73adec8139b6fe30452e78f210f27b1f346b8877" @@ -10517,6 +10709,14 @@ node-ensure@^0.0.0: resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc= +node-environment-flags@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" + integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== + dependencies: + object.getownpropertydescriptors "^2.0.3" + semver "^5.7.0" + node-fetch@^2.1.2, node-fetch@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" @@ -10781,7 +10981,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0: +object.assign@4.1.0, object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== @@ -10899,6 +11099,11 @@ opencollective-postinstall@^2.0.2: resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89" integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw== +opener@^1.4.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" + integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA== + opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -12752,6 +12957,16 @@ react-dom@^16.7.0, react-dom@^16.8.3: prop-types "^15.6.2" scheduler "^0.18.0" +react-dom@^16.8.5: + version "16.13.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.0.tgz#cdde54b48eb9e8a0ca1b3dc9943d9bb409b81866" + integrity sha512-y09d2c4cG220DzdlFkPTnVvGTszVvNpC73v+AaLGLHbkpy3SSgvYq8x0rNwPJ/Rk/CicTNgk0hbHNw1gMEZAXg== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.0" + react-draggable@^4.0.3: version "4.2.0" resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.2.0.tgz#40cc5209082ca7d613104bf6daf31372cc0e1114" @@ -13102,6 +13317,15 @@ react@^16.12.0, react@^16.8.3: object-assign "^4.1.1" prop-types "^15.6.2" +react@^16.8.5: + version "16.13.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.13.0.tgz#d046eabcdf64e457bbeed1e792e235e1b9934cf7" + integrity sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + reactcss@^1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" @@ -13201,6 +13425,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== + dependencies: + picomatch "^2.0.4" + readdirp@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" @@ -13888,6 +14119,14 @@ scheduler@^0.18.0: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.0.tgz#a715d56302de403df742f4a9be11975b32f5698d" + integrity sha512-xowbVaTPe9r7y7RUejcK73/j8tt2jfiyTednOvHbA8JoClvMYCp+r8QegLwK/n8zWQAtZb1fFnER4XLBZXrCxA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + schema-utils@^0.4.0: version "0.4.7" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" @@ -13943,7 +14182,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -14723,6 +14962,11 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" +strip-json-comments@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + strip-json-comments@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" @@ -14795,6 +15039,13 @@ supports-color@5.5.0, supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" +supports-color@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" + integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== + 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" @@ -14885,6 +15136,18 @@ tar@^2.0.0: fstream "^1.0.12" inherits "2" +tcomb-validation@^3.3.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tcomb-validation/-/tcomb-validation-3.4.1.tgz#a7696ec176ce56a081d9e019f8b732a5a8894b65" + integrity sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA== + dependencies: + tcomb "^3.0.0" + +tcomb@^3.0.0, tcomb@^3.2.17: + version "3.2.29" + resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-3.2.29.tgz#32404fe9456d90c2cf4798682d37439f1ccc386c" + integrity sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ== + telejson@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/telejson/-/telejson-3.3.0.tgz#6d814f3c0d254d5c4770085aad063e266b56ad03" @@ -15639,6 +15902,11 @@ uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.1.tgz#95ed6ff3d8c881cbf85f0f05cc3915ef994818ef" + integrity sha512-yqjRXZzSJm9Dbl84H2VDHpM3zMjzSJQ+hn6C4zqd5ilW+7P4ZmLEEqwho9LjP+tGuZlF4xrHQXT0h9QZUS/pWA== + v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" @@ -15652,6 +15920,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@^10.11.0: + version "10.11.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" + integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== + value-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" @@ -15997,7 +16270,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@1, which@^1.2.9, which@^1.3.0, which@^1.3.1: +which@1, which@1.3.1, which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -16011,7 +16284,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: +wide-align@1.1.3, wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== @@ -16293,6 +16566,11 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= + xmlchars@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" @@ -16335,18 +16613,18 @@ yaml@^1.7.2: dependencies: "@babel/runtime" "^7.6.3" -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== +yargs-parser@13.1.1, yargs-parser@^13.1.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" + integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^13.1.1: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== +yargs-parser@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" + integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" @@ -16366,6 +16644,15 @@ yargs-parser@^5.0.0: dependencies: camelcase "^3.0.0" +yargs-unparser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" + integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== + dependencies: + flat "^4.1.0" + lodash "^4.17.15" + yargs "^13.3.0" + yargs@12.0.5: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" @@ -16384,7 +16671,7 @@ yargs@12.0.5: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" -yargs@^13.3.0: +yargs@13.3.0, yargs@^13.2.2, yargs@^13.3.0: version "13.3.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==