Merge branch 'release' into 'master'

Release to master

See merge request theappsmith/internal-tools-client!372
This commit is contained in:
Satbir Singh 2020-03-12 02:22:10 +00:00
commit 2b2672af57
60 changed files with 944 additions and 2277 deletions

5
app/client/.dockerignore Normal file
View File

@ -0,0 +1,5 @@
.git
.idea
node_modules
build
build.tgz

View File

@ -28,3 +28,5 @@ yarn-error.log*
/src/assets/icons/fonts/*
.idea
.storybook-out/
cypress/videos
results/

83
app/client/.gitlab-ci.yml Normal file
View File

@ -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

22
app/client/Dockerfile Normal file
View File

@ -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;"]

View File

@ -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"

View File

@ -1 +1,11 @@
{}
{
"baseUrl":"http://dev.appsmith.com:3000/",
"reporter": "mochawesome",
"reporterOptions": {
"reportDir": "results",
"overwrite": false,
"html": false,
"json": true
}
}

View File

@ -0,0 +1,6 @@
{
"username": "testowner@appsmith.com",
"password": "own3rT3st1ng",
}

View File

@ -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()
})
})

View File

@ -1,309 +0,0 @@
/// <reference types="Cypress" />
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 <select> 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 });
});
});

View File

@ -1,46 +0,0 @@
/// <reference types="Cypress" />
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);
});
});

View File

@ -1,168 +0,0 @@
/// <reference types="Cypress" />
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 <td> 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 <td> 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-elements-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");
});
});
});

View File

@ -1,96 +0,0 @@
/// <reference types="Cypress" />
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);
});
});
});
});
});

View File

@ -1,79 +0,0 @@
/// <reference types="Cypress" />
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");
});
});

View File

@ -1,228 +0,0 @@
/// <reference types="Cypress" />
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"]);
});
});

View File

@ -1,119 +0,0 @@
/// <reference types="Cypress" />
/// 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");
});
});
});

View File

@ -1,58 +0,0 @@
/// <reference types="Cypress" />
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");
});
});
});

View File

@ -1,34 +0,0 @@
/// <reference types="Cypress" />
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");
});
});

View File

@ -1,93 +0,0 @@
/// <reference types="Cypress" />
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");
});
});

View File

@ -1,60 +0,0 @@
/// <reference types="Cypress" />
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;
},
});
});
});

View File

@ -1,217 +0,0 @@
/// <reference types="Cypress" />
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);
});
});

View File

@ -1,114 +0,0 @@
/// <reference types="Cypress" />
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();
});
});
});

View File

@ -1,98 +0,0 @@
/// <reference types="Cypress" />
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");
});
});

View File

@ -1,140 +0,0 @@
/// <reference types="Cypress" />
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);
});
});

View File

@ -1,145 +0,0 @@
/// <reference types="Cypress" />
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 <img> element and set its src to the dataUrl
const img = Cypress.$("<img />", { 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<string>
*/
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;
}),
);
});
});

View File

@ -1,63 +0,0 @@
/// <reference types="Cypress" />
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)
});
});

View File

@ -1,35 +0,0 @@
/// <reference types="Cypress" />
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);
});
});

View File

@ -1,24 +0,0 @@
/// <reference types="Cypress" />
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");
});
});

View File

@ -0,0 +1,5 @@
{
"username":"input[name='username']",
"password":"input[name='password']",
"submitBtn":"button[type='submit']"
}

View File

@ -1,3 +1,4 @@
/// <reference types="cypress" />
// ***********************************************************
// 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
};
}

View File

@ -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()
})

1
app/client/cypress/test.sh Executable file
View File

@ -0,0 +1 @@
$(npm bin)/cypress run CYPRESS_BASE_URL='http://dev.appsmith.com:3000/' --spec "cypress/integration/OnBoarding/Login_spec.js"

View File

@ -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;
#}
}

View File

@ -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",

View File

@ -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"
}
}
}

View File

@ -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`;
}

View File

@ -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);

View File

@ -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 (
<SidebarWrapper>
<Switch>
<Route exact path={BUILDER_URL} component={WidgetSidebar} />
<Route exact path={API_EDITOR_URL()} component={ApiSidebar} />
<Route exact path={API_EDITOR_ID_URL()} component={ApiSidebar} />
<Route
<AppRoute
exact
path={BUILDER_URL}
component={WidgetSidebar}
name={"WidgetSidebar"}
/>
<AppRoute
exact
path={API_EDITOR_URL()}
component={ApiSidebar}
name={"ApiSidebar"}
/>
<AppRoute
exact
path={API_EDITOR_ID_URL()}
component={ApiSidebar}
name={"ApiSidebar"}
/>
<AppRoute
exact
path={PAGE_LIST_EDITOR_URL()}
component={PageListSidebar}
name={"PageListSidebar"}
/>
</Switch>
</SidebarWrapper>

View File

@ -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(
<Router history={history}>
<Suspense fallback={loadingIndicator}>
<Switch>
<ProtectedRoute exact path={BASE_URL} component={App} />
<ProtectedRoute path={ORG_URL} component={Organization} />
<ProtectedRoute exact path={USERS_URL} component={Users} />
<Route path={USER_AUTH_URL} component={UserAuth} />
<AppRoute
exact
path={BASE_URL}
component={App}
name={"App"}
routeProtected
/>
<AppRoute
path={ORG_URL}
component={Organization}
name={"Organisation"}
routeProtected
/>
<AppRoute
exact
path={USERS_URL}
component={Users}
name={"Users"}
routeProtected
/>
<AppRoute
path={USER_AUTH_URL}
component={UserAuth}
name={"UserAuth"}
/>
<Redirect exact from={BASE_LOGIN_URL} to={AUTH_LOGIN_URL} />
<Redirect exact from={BASE_SIGNUP_URL} to={SIGN_UP_URL} />
<ProtectedRoute
<AppRoute
exact
path={APPLICATIONS_URL}
component={Applications}
name={"Home"}
routeProtected
/>
<ProtectedRoute path={BUILDER_URL} component={Editor} />
<ProtectedRoute path={APP_VIEW_URL} component={AppViewer} />
<Route component={PageNotFound} />
<AppRoute
path={BUILDER_URL}
component={Editor}
name={"Editor"}
routeProtected
/>
<AppRoute
path={APP_VIEW_URL}
component={AppViewer}
name={"AppViewer"}
routeProtected
logDisable
/>
<AppRoute component={PageNotFound} name={"PageNotFound"} />
</Switch>
</Suspense>
</Router>

View File

@ -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<WidgetProps>;
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 (
<PageView width={props.dsl.rightColumn}>
{props.dsl.widgetId &&

View File

@ -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<WidgetProps>;
currentPageName?: string;
fetchPage: (pageId: string) => void;
} & RouteComponentProps<AppViewerRouteParams>;
@ -77,7 +81,11 @@ class AppViewerPageContainer extends Component<AppViewerPageContainerProps> {
} else if (!this.props.isFetchingPage && this.props.widgets) {
return (
<Section>
<AppPage dsl={this.props.widgets} />
<AppPage
dsl={this.props.widgets}
pageId={this.props.match.params.pageId}
pageName={this.props.currentPageName}
/>
</Section>
);
}
@ -87,6 +95,7 @@ class AppViewerPageContainer extends Component<AppViewerPageContainerProps> {
const mapStateToProps = (state: AppState) => ({
isFetchingPage: getIsFetchingPage(state),
widgets: getCanvasWidgetDsl(state),
currentPageName: getCurrentPageName(state),
});
const mapDispatchToProps = (dispatch: any) => ({

View File

@ -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<
<SideNav items={items} active={this.props.currentDSLPageId} />
</AppViewerSideNavWrapper>
<Switch>
<Route
<AppRoute
path={getApplicationViewerPageURL()}
exact
component={AppViewerPageContainer}
name={"AppViewerPageContainer"}
logDisable
/>
</Switch>
</AppViewerBody>

View File

@ -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<Props> {
};
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,
});

View File

@ -138,7 +138,12 @@ class ApiSidebar extends React.Component<Props> {
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);
};

View File

@ -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,
};

View File

@ -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<Props, State> {
this.props.deleteItem(
item.id,
item.name,
page.name,
),
label: "Delete",
intent: "danger",

View File

@ -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);
},

View File

@ -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),
};
};

View File

@ -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}
>
<Switch>
<Route exact path={API_EDITOR_URL()} component={ApiEditor} />
<Route exact path={API_EDITOR_ID_URL()} component={ApiEditor} />
<AppRoute
exact
path={API_EDITOR_URL()}
component={ApiEditor}
name={"ApiEditor"}
/>
<AppRoute
exact
path={API_EDITOR_ID_URL()}
component={ApiEditor}
name={"ApiEditor"}
/>
</Switch>
</DrawerWrapper>
</Wrapper>

View File

@ -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",
});
}}
/>
</FormActions>
</SpacedSubmitForm>
<Divider />
<ThirdPartyAuth
type={"SIGNIN"}
logins={[SocialLoginTypes.GOOGLE, SocialLoginTypes.GITHUB]}
/>
</AuthCardBody>

View File

@ -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<SignupFormValues>) => {
intent="primary"
filled
size="large"
onClick={() => {
AnalyticsUtil.logEvent("SIGNUP_CLICK", {
signupMethod: "EMAIL",
});
}}
/>
</FormActions>
</SpacedForm>
<Divider />
<ThirdPartyAuth
logins={[SocialLoginTypes.GOOGLE, SocialLoginTypes.GITHUB]}
type={"SIGNUP"}
/>
</AuthCardBody>

View File

@ -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<string, string> = {
GITHUB: "github",
};
type SignInType = "SIGNIN" | "SIGNUP";
const SocialLoginButton = (props: {
logo: string;
name: string;
url: string;
type: SignInType;
}) => {
return (
<StyledSocialLoginButton href={props.url}>
<StyledSocialLoginButton
href={props.url}
onClick={() => {
let eventName: EventName = "LOGIN_CLICK";
if (props.type === "SIGNUP") {
eventName = "SIGNUP_CLICK";
}
AnalyticsUtil.logEvent(eventName, {
loginMethod: props.name.toUpperCase(),
});
}}
>
<div>
<img alt={` ${props.name} login`} src={props.logo} />
</div>
@ -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 <SocialLoginButton key={item.name} {...item}></SocialLoginButton>;
return (
<SocialLoginButton
key={item.name}
{...item}
type={props.type}
></SocialLoginButton>
);
},
);
return <ThirdPartyAuthWrapper>{socialLoginButtons}</ThirdPartyAuthWrapper>;

View File

@ -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 = () => {
<Centered>
<AuthCard>
<Switch location={location}>
<Route exact path={`${path}/login`} component={Login} />
<Route exact path={`${path}/signup`} component={SignUp} />
<Route
<AppRoute
exact
path={`${path}/login`}
component={Login}
name={"Login"}
/>
<AppRoute
exact
path={`${path}/signup`}
component={SignUp}
name={"SignUp"}
/>
<AppRoute
exact
path={`${path}/resetPassword`}
component={ResetPassword}
name={"ResetPassword"}
/>
<Route
<AppRoute
exact
path={`${path}/forgotPassword`}
component={ForgotPassword}
name={"ForgotPassword"}
/>
<Route
<AppRoute
exact
path={`${path}/createPassword`}
component={CreatePassword}
name={"CreatePassword"}
/>
</Switch>
</AuthCard>

View File

@ -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 (
<Route
{...rest}
render={props => (
<WrappedComponent {...props}>
<Component {...props} />
</WrappedComponent>
)}
render={props => {
return rest.routeProtected ? (
<WrappedComponent {...props}>
<Component {...props} />
</WrappedComponent>
) : (
<Component {...props}></Component>
);
}}
/>
);
};
export default ProtectedRoute;
export default AppRoute;

View File

@ -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 (
<PageWrapper displayName="Organization Settings">
<Switch location={location}>
<Route exact path={`${path}/settings`} component={Settings} />
<Route exact path={`${path}/invite`} component={Invite} />
<Route component={DefaultOrgPage} />
<AppRoute
exact
path={`${path}/settings`}
component={Settings}
name={"Settings"}
/>
<AppRoute
exact
path={`${path}/invite`}
component={Invite}
name={"Invite"}
/>
<AppRoute component={DefaultOrgPage} name={"DefaultOrgPage"} />
</Switch>
</PageWrapper>
);

View File

@ -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 {

View File

@ -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,
});

View File

@ -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: {

View File

@ -48,7 +48,7 @@ class FilePickerWidget extends BaseWidget<FilePickerWidgetProps, WidgetState> {
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:

View File

@ -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 };
// });
// });

View File

@ -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==