Merge branch 'release' into 'master'
Release to master See merge request theappsmith/internal-tools-client!372
This commit is contained in:
commit
2b2672af57
5
app/client/.dockerignore
Normal file
5
app/client/.dockerignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.git
|
||||
.idea
|
||||
node_modules
|
||||
build
|
||||
build.tgz
|
||||
2
app/client/.gitignore
vendored
2
app/client/.gitignore
vendored
|
|
@ -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
83
app/client/.gitlab-ci.yml
Normal 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
22
app/client/Dockerfile
Normal 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;"]
|
||||
|
|
@ -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"
|
||||
|
|
@ -1 +1,11 @@
|
|||
{}
|
||||
{
|
||||
"baseUrl":"http://dev.appsmith.com:3000/",
|
||||
|
||||
"reporter": "mochawesome",
|
||||
"reporterOptions": {
|
||||
"reportDir": "results",
|
||||
"overwrite": false,
|
||||
"html": false,
|
||||
"json": true
|
||||
}
|
||||
}
|
||||
6
app/client/cypress/fixtures/user.json
Normal file
6
app/client/cypress/fixtures/user.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
{
|
||||
"username": "testowner@appsmith.com",
|
||||
"password": "own3rT3st1ng",
|
||||
|
||||
}
|
||||
20
app/client/cypress/integration/OnBoarding/Login_spec.js
Normal file
20
app/client/cypress/integration/OnBoarding/Login_spec.js
Normal 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()
|
||||
|
||||
})
|
||||
})
|
||||
|
|
@ -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 });
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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-element’s-text-contents
|
||||
});
|
||||
|
||||
it(".and() - chain multiple assertions together", () => {
|
||||
// https://on.cypress.io/and
|
||||
cy.get(".assertions-link")
|
||||
.should("have.class", "active")
|
||||
.and("have.attr", "href")
|
||||
.and("include", "cypress.io");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Explicit Assertions", () => {
|
||||
// https://on.cypress.io/assertions
|
||||
it("expect - make an assertion about a specified subject", () => {
|
||||
// We can use Chai's BDD style assertions
|
||||
expect(true).to.be.true;
|
||||
const o = { foo: "bar" };
|
||||
|
||||
expect(o).to.equal(o);
|
||||
expect(o).to.deep.equal({ foo: "bar" });
|
||||
// matching text using regular expression
|
||||
expect("FooBar").to.match(/bar$/i);
|
||||
});
|
||||
|
||||
it("pass your own callback function to should()", () => {
|
||||
// Pass a function to should that can have any number
|
||||
// of explicit assertions within it.
|
||||
// The ".should(cb)" function will be retried
|
||||
// automatically until it passes all your explicit assertions or times out.
|
||||
cy.get(".assertions-p")
|
||||
.find("p")
|
||||
.should($p => {
|
||||
// https://on.cypress.io/$
|
||||
// return an array of texts from all of the p's
|
||||
// @ts-ignore TS6133 unused variable
|
||||
const texts = $p.map((i, el) => Cypress.$(el).text());
|
||||
|
||||
// jquery map returns jquery object
|
||||
// and .get() convert this to simple array
|
||||
const paragraphs = texts.get();
|
||||
|
||||
// array should have length of 3
|
||||
expect(paragraphs, "has 3 paragraphs").to.have.length(3);
|
||||
|
||||
// use second argument to expect(...) to provide clear
|
||||
// message with each assertion
|
||||
expect(paragraphs, "has expected text in each paragraph").to.deep.eq([
|
||||
"Some text from first p",
|
||||
"More text from second p",
|
||||
"And even more text from third p",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("finds element by class name regex", () => {
|
||||
cy.get(".docs-header")
|
||||
.find("div")
|
||||
// .should(cb) callback function will be retried
|
||||
.should($div => {
|
||||
expect($div).to.have.length(1);
|
||||
|
||||
const className = $div[0].className;
|
||||
|
||||
expect(className).to.match(/heading-/);
|
||||
})
|
||||
// .then(cb) callback is not retried,
|
||||
// it either passes or fails
|
||||
.then($div => {
|
||||
expect($div, "text content").to.have.text("Introduction");
|
||||
});
|
||||
});
|
||||
|
||||
it("can throw any error", () => {
|
||||
cy.get(".docs-header")
|
||||
.find("div")
|
||||
.should($div => {
|
||||
if ($div.length !== 1) {
|
||||
// you can throw your own errors
|
||||
throw new Error("Did not find 1 element");
|
||||
}
|
||||
|
||||
const className = $div[0].className;
|
||||
|
||||
if (!className.match(/heading-/)) {
|
||||
throw new Error(`Could not find class "heading-" in ${className}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("matches unknown text between two elements", () => {
|
||||
/**
|
||||
* Text from the first element.
|
||||
* @type {string}
|
||||
*/
|
||||
let text;
|
||||
|
||||
/**
|
||||
* Normalizes passed text,
|
||||
* useful before comparing text with spaces and different capitalization.
|
||||
* @param {string} s Text to normalize
|
||||
*/
|
||||
const normalizeText = s => s.replace(/\s/g, "").toLowerCase();
|
||||
|
||||
cy.get(".two-elements")
|
||||
.find(".first")
|
||||
.then($first => {
|
||||
// save text from the first element
|
||||
text = normalizeText($first.text());
|
||||
});
|
||||
|
||||
cy.get(".two-elements")
|
||||
.find(".second")
|
||||
.should($div => {
|
||||
// we can massage text before comparing
|
||||
const secondText = normalizeText($div.text());
|
||||
|
||||
expect(secondText, "second text").to.equal(text);
|
||||
});
|
||||
});
|
||||
|
||||
it("assert - assert shape of an object", () => {
|
||||
const person = {
|
||||
name: "Joe",
|
||||
age: 20,
|
||||
};
|
||||
|
||||
assert.isObject(person, "value is object");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
@ -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"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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)
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
5
app/client/cypress/locators/LoginPage.json
Normal file
5
app/client/cypress/locators/LoginPage.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"username":"input[name='username']",
|
||||
"password":"input[name='password']",
|
||||
"submitBtn":"button[type='submit']"
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
1
app/client/cypress/test.sh
Executable file
|
|
@ -0,0 +1 @@
|
|||
$(npm bin)/cypress run CYPRESS_BASE_URL='http://dev.appsmith.com:3000/' --spec "cypress/integration/OnBoarding/Login_spec.js"
|
||||
46
app/client/docker/nginx.conf
Normal file
46
app/client/docker/nginx.conf
Normal 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;
|
||||
#}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
105
app/client/results/mochawesome.json
Normal file
105
app/client/results/mochawesome.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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) => ({
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
// });
|
||||
// });
|
||||
|
|
@ -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==
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user